【问题标题】:How to implement a parallel map in swift如何快速实现并行地图
【发布时间】:2017-03-06 07:04:28
【问题描述】:

我有以下代码:-

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
    }
}

在此处的return语句res中,它经常在部分执行完成的情况下返回。有道理,并发 Perform 是非阻塞的。我不确定如何继续等待它。我应该使用调度组/期望之类的东西还是有一些更简单更优雅的方法?本质上,我正在快速寻找一个简单的等待通知抽象。

【问题讨论】:

    标签: ios swift concurrency parallel-processing


    【解决方案1】:

    @user28434 的回答很棒,使用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! })
        }
    }
    

    它与在我正在运行的任务上使用 OperationQueue() 相比(在 10,000 个项目上并行运行我的某些功能)平均使用 100 次运行:

    • concurrentPerform 平均耗时 0.060 秒。
    • OperationQueue() 平均耗时 0.087 秒。

    【讨论】:

    • 相当不错,甚至比我的还快?。只有几点说明:应该是Collection 扩展名,而不仅仅是Array
    • @user28434 NSRecursiveLock 比我用的更快更好,从现在开始我会用它?
    • 问题中的初始问题指出 concurrentPerform 是非阻塞的,因此当您返回时并非一切都已完成。这不会有同样的问题吗?
    • 是的,我自己试过了,它肯定会等待,所以不确定 OPs 代码出了什么问题!
    • @AdamA 因为我们创建res 已经是完整的长度,所以它不会重新分配。我真的看不出有必要锁定那里的理由。是的,追加你需要锁定。我认为我在那里被锁的原因是因为@user28434 的回答..
    【解决方案2】:

    你可以试试这样的:

    // 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! })
    }
    

    【讨论】:

    • 为什么不返回 [R] 而不是 [R?] ?我试着用res.map({ $0! }) 强制它崩溃了,这不应该是这样吧?
    • @RodrigoRuiz,是的,可以重写为返回 [R]。你得到的崩溃:它发生在操场上吗? '因为我只能在操场上重现它,如果我添加一些随机的额外代码(如print),在map 之前使用本地res 变量和$0!,即使在操场上它也可以正常工作。我猜这是臭名昭著的游乐场内存管理。
    • 当我进行 10000 次操作时,它崩溃了。知道为什么吗?
    • return res.compactMap({$0})
    • @Hadus,通过添加递归锁进行修改。与DispatchQueue.sync 一样工作,但没有额外的包装我们需要的功能。
    猜你喜欢
    • 2015-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多