There's quite a few threads on this however Using Grand Central Dispatch in Swift to parallelize and speed up “for" loops? uses Swift <3.0 code and I can't get the equivalent to work in 3 (see code). Process Array in parallel using GCD uses pointers and it gets a bit ugly so I'm going to assert right here that I'm looking for the nice Swift 3 way to do it (as efficiently as possible of course). I also heard groups are slow (?) maybe someone can confirm that. I couldn't get groups to work either.
Here is my implementation of a striding parallel map function (in an extension of Array). It want's to execute on the global queue so as to not block the UI. It may be that the concurrent bit doesn't need to be in the scope, only the remainder loop.
extension Array {
func parallelMap<R>(striding n: Int, f: @escaping (Element) -> R, completion: @escaping ([R]) -> ()) {
let N = self.count
var res = [R?](repeating: nil, count: self.count)
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.concurrentPerform(iterations: N/n) {k in
for i in (k * n)..<((k + 1) * n) {
res[i] = f(self[i]) //Error 1 here
}
}
//remainder loop
for i in (N - (N % n))..<N {
res[i] = f(self[i])
}
DispatchQueue.main.sync { //But it will pause on this line.
print("\nPlease work!\n") //This doesn't execute either.
completion(unwrap(res)!) //Error 2 here, or rather "not here"
}
}
}
}
public func unwrap<T>(_ arr: Array<T?>) -> Array<T>? {
if (arr.contains{$0 == nil}) {
return nil
} else {
return arr.map{(x: T?) -> T in x!}
}
}
Error 1: our old friend EXC_BAD_ACCESS
on the inner array assignment line about half the times I test it. I'm guessing this suggests a simultaneous access issue.
Error 2: completion
never executes!
Error 3: errors go on forever, I'm sure this will happen once the above are fixed.
Finally: code for the fastest parallel (making sure it's as parallel as possible, I don't like the 'concurrent' in my code) map/for function possible. This is in addition to fixing my code.
Martin's original approach is still the right way to do this. Merging your approach with his and converting to Swift 3 is fairly straightforward (though I got rid of your optionals and just handled the memory by hand).
extension Array {
func parallelMap<R>(striding n: Int, f: @escaping (Element) -> R, completion: @escaping ([R]) -> ()) {
let N = self.count
let res = UnsafeMutablePointer<R>.allocate(capacity: N)
DispatchQueue.concurrentPerform(iterations: N/n) { k in
for i in (k * n)..<((k + 1) * n) {
res[i] = f(self[i])
}
}
for i in (N - (N % n))..<N {
res[i] = f(self[i])
}
let finalResult = Array<R>(UnsafeBufferPointer(start: res, count: N))
res.deallocate(capacity: N)
DispatchQueue.main.async {
completion(finalResult)
}
}
}
Martin's version avoids the extra copy because he has a "zero" value to initialize the array to. If you know your type has a trivial init()
, you can avoid the extra copy:
protocol TriviallyInitializable {
init()
}
extension Array {
func parallelMap<R>(striding n: Int, f: @escaping (Element) -> R, completion: @escaping ([R]) -> ()) where R: TriviallyInitializable {
let N = self.count
var finalResult = Array<R>(repeating: R(), count: N)
finalResult.withUnsafeMutableBufferPointer { res in
DispatchQueue.concurrentPerform(iterations: N/n) { k in
for i in (k * n)..<((k + 1) * n) {
res[i] = f(self[i])
}
}
}
for i in (N - (N % n))..<N {
finalResult[i] = f(self[i])
}
DispatchQueue.main.async {
completion(finalResult)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With