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:
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