Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 3 Parallel for/map Loop

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.

like image 676
Richard Birkett Avatar asked Dec 19 '16 02:12

Richard Birkett


1 Answers

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)
        }
    }
}
like image 119
Rob Napier Avatar answered Nov 04 '22 02:11

Rob Napier