How to implement a parallel map in swift

|▌冷眼眸甩不掉的悲伤 提交于 2021-02-10 14:15:41

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!