In my program I heavily rely in the following pattern that I think is very common. There is a structure that holds data (lets say Double):
struct Container {
var data: Double
}
Next, an array of that struct is created, potentially big:
let containers = (0...<10_000_000).map { _ in
Container(data: .random(in: -10...10))
}
Then, a (map)reduce operation is applied to the array to extract a value, for instance:
let sum = containers.reduce(0.0) { $0 + $1.data }
I first thought that this particular operation would be fast because the operation is simple enough for the compiler to optimize bounds checks and other slow operations: there is no weird index computations, no capture in the closure, everything is immutable, etc...
But then, I compared performance against this unsafe implementation of reduce on arrays:
extension Array {
func unsafeReduce<Result>(
_ initialResult: Result,
_ nextPartialResult: (Result, Element) throws -> Result
) rethrows -> Result {
try self.withUnsafeBufferPointer { buffer -> Result in
try buffer.reduce(initialResult, nextPartialResult)
}
}
}
I compiled both programs with full optimizations -O -unchecked and I also used Xcode profiler to time programs, it appears that the unsafe reduce is 16x faster than its safe counterpart: 200ms vs 4s.
My questions are:
- First, is my unsafe implementation right? What are the potential issued in using this version?
- What is the cause of the slowdown? I tried to have a look at the assembly code with Compiler Explorer and with Xcode profiler's call tree but can't figure out what is the exact cause. Can someone explain step by step what exactly the program does?
I'm using Xcode 11.5 with Swift 5 on a MacBook Air mid-2015.
Edit
I ported the code to Python and without using Numpy for any computation it is still 2 times faster than Swift reduce operation. I did not check the memory consumption, though.