Home > Software engineering >  Why is it getting slower and slower each time I call the formatted() method of the Decimal type in S
Why is it getting slower and slower each time I call the formatted() method of the Decimal type in S

Time:01-13

I needed to separate integers into 3-digit numbers and looked into the formatted() method of the Decimal type.
Calling the formatted() method of type Decimal 100,000 times each for a random integer gradually degrades performance.
I would like to know why this happens.

import Foundation

/// https://stackoverflow.com/a/56381954
func calculateTime(block : (() -> Void)) {
  let start = DispatchTime.now()
  block()
  let end = DispatchTime.now()
  let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
  let timeInterval = Double(nanoTime) / 1_000_000_000
  print("Time: \(timeInterval) seconds")
}

calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }
Time: 0.9492465 seconds
Time: 3.29213125 seconds
Time: 7.988363667 seconds
Time: 15.165178292 seconds
Time: 17.305036583 seconds
Time: 25.0114935 seconds
Time: 35.746310417 seconds
Time: 47.024551125 seconds

CodePudding user response:

While matt and Martin R are correct, they din't actually point to the culprit. When you use the "Memory Object" Debugger, while the functions are executing, you will see this very interesting graph:

enter image description here

So, the function formatted() causes a LOT of allocations, mainly objects of type NSAutoLocale.

This hints to further issues: since evaluating a locale requires to access a file where the locale is actually read from, you may also experience very slow performance.

So, if you want to speed it up, too, you should explicitly use a pre-configured locale object, which you allocate once, and only once, which you then use as a parameter to format the string.

Edit:

In order to prove my hypothesis, I created an equivalent statement and compared that with your original:

Your original code:

calculateTime { for _ in 0...100_000 { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } }

Time: 9.674371748 seconds

The equivalent code using a single locale:

let locale = Locale()
calculateTime { for _ in 0...100_000 { _ = NSDecimalNumber(
    value: Int.random(in: 0...Int.max))
    .description(withLocale: locale)
} }

Time: 0.210493037 seconds

AND without any further modifications, calling the improved statement repeatedly, you get this:

Time: 0.224133942 seconds
Time: 0.238930039 seconds
Time: 0.214735965 seconds
Time: 0.220390686 seconds
Time: 0.212360066 seconds
Time: 0.207630215 seconds
Time: 0.205125154 seconds

...

CodePudding user response:

It's a Heisenbug: you are causing the slowdown yourself by straining memory with your test harness. Change your test lines to:

calculateTime { for _ in 0...100_000 { autoreleasepool { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } } }
calculateTime { for _ in 0...100_000 { autoreleasepool { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } } }
calculateTime { for _ in 0...100_000 { autoreleasepool { _ = Decimal(Int.random(in: 0...Int.max)).formatted() } } }
// ... and so on
  • Related