问题
I have the following code:-
extension Collection {
// EZSE : A parralelized map for collections, operation is non blocking
public func pmap<R>(_ each: (Self.Iterator.Element) -> R) -> [R?] {
let indices = indicesArray()
var res = [R?](repeating: nil, count: indices.count)
DispatchQueue.concurrentPerform(iterations: indices.count) { (index) in
let elementIndex = indices[index]
res[index] = each(self[elementIndex])
}
// Above code is non blocking so partial exec on most runs
return res
}
/// EZSE : Helper method to get an array of collection indices
private func indicesArray() -> [Self.Index] {
var indicesArray: [Self.Index] = []
var nextIndex = startIndex
while nextIndex != endIndex {
indicesArray.append(nextIndex)
nextIndex = index(after: nextIndex)
}
return indicesArray
}
}
In here at the return statement res, it often returns with part of the execution completed. Makes sense, concurrent Perform is non blocking. I am unsure how to proceed to wait for it. Should I use something like a dispatch group/expectations or is there some simpler more elegant method? Essentially I am looking for a simple wait notify abstraction in swift.
回答1:
You can try something like this:
// EZSE : A parralelized map for collections, operation is non blocking
public func pmap<R>(_ each: @escaping (Self.Iterator.Element) -> R) -> [R] {
let totalCount = indices.count
var res = ContiguousArray<R?>(repeating: nil, count: totalCount)
let queue = OperationQueue()
queue.maxConcurrentOperationCount = totalCount
let lock = NSRecursiveLock()
indices
.enumerated()
.forEach { index, elementIndex in
queue.addOperation {
let temp = each(self[elementIndex])
lock.lock()
res[index] = temp
lock.unlock()
}
}
queue.waitUntilAllOperationsAreFinished()
return res.map({ $0! })
}
回答2:
@user28434's answer is great and it can be made even faster by using concurrentPerform
:
extension Collection {
func parallelMap<R>(_ transform: @escaping (Element) -> R) -> [R] {
var res: [R?] = .init(repeating: nil, count: count)
let lock = NSRecursiveLock()
DispatchQueue.concurrentPerform(iterations: count) { i in
let result = transform(self[index(startIndex, offsetBy: i)])
lock.lock()
res[i] = result
lock.unlock()
}
return res.map({ $0! })
}
}
It compares to using OperationQueue()
on the task I'm running (running some function of mine on 10,000 items in parallel) Using 100 runs to average:
concurrentPerform
takes 0.060 seconds on average.OperationQueue()
takes 0.087 seconds on average.
来源:https://stackoverflow.com/questions/42619447/how-to-implement-a-parallel-map-in-swift