使用GCD并行处理数组

时间:2014-11-01 22:19:38

标签: swift parallel-processing grand-central-dispatch

我有一个大型数组,我希望通过将它的片段交给几个异步任务来处理。作为概念证明,我编写了以下代码:

class TestParallelArrayProcessing {
    let array: [Int]
    var summary: [Int]

    init() {
        array = Array<Int>(count: 500000, repeatedValue: 0)
        for i in 0 ..< 500000 {
            array[i] = Int(arc4random_uniform(10))
        }
        summary = Array<Int>(count: 10, repeatedValue: 0)
    }

    func calcSummary() {
        let group = dispatch_group_create()
        let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)

        for i in 0 ..< 10 {
            dispatch_group_async(group, queue, {
                let base = i * 50000
                for x in base ..< base + 50000 {
                    self.summary[i] += self.array[x]
                }
            })
        }
        dispatch_group_notify(group, queue, {
            println(self.summary)
        })
    }
}

init()之后,array将使用0到9之间的随机整数进行初始化。

calcSummary函数调度10个任务,这些任务从array中取出50000个项目的不相交块,并使用summary中的相应插槽作为累加器将其相加。

此程序在self.summary[i] += self.array[x]行崩溃。错误是:

 EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP).

我可以在调试器中看到它在崩溃之前已经设法迭代了几次,并且在崩溃时变量的值都在正确的范围内。

我已经读过在尝试访问已经发布的对象时可能会发生EXC_I386_INVOP。我想知道这是否与Swift制作数组的副本有关,如果它被修改,如果是这样,如何避免它。

5 个答案:

答案 0 :(得分:5)

使用Array类型的withUnsafeMutableBufferPointer<R>(body: (inout UnsafeMutableBufferPointer<T>) -> R) -> R方法,这与@ Eduardo的回答略有不同。 That method's documentation states

  

调用body(p),其中p是指向Array可变连续存储的指针。如果不存在此类存储,则首先创建它。

     

通常,优化器可以消除数组算法中的边界和唯一性检查,但是当失败时,在body的参数上调用相同的算法可以让您以安全速度进行交易。

第二段似乎正是这里发生的事情,所以使用这种方法在Swift中可能更“惯用”,无论这意味着什么:

func calcSummary() {
    let group = dispatch_group_create()
    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)

    self.summary.withUnsafeMutableBufferPointer {
        summaryMem -> Void in
        for i in 0 ..< 10 {
            dispatch_group_async(group, queue, {
                let base = i * 50000
                for x in base ..< base + 50000 {
                    summaryMem[i] += self.array[x]
                }
            })
        }
    }

    dispatch_group_notify(group, queue, {
        println(self.summary)
    })
}

答案 1 :(得分:3)

当你使用+=运算符时,LHS是一个inout参数 - 我认为你正在获得竞争条件,正如你在更新中提到的那样,Swift在数组中移动以进行优化。通过将局部变量中的块相加,然后简单地分配到summary中的正确索引,我能够使它工作:

func calcSummary() {
    let group =  dispatch_group_create()
    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)

    for i in 0 ..< 10 {
        dispatch_group_async(group, queue, {
            let base = i * 50000
            var sum = 0
            for x in base ..< base + 50000 {
                sum += self.array[x]
            }
            self.summary[i] = sum
        })
    }

    dispatch_group_notify(group, queue, {
        println(self.summary)
    })
}

答案 2 :(得分:2)

我认为Nate是对的:summary变量存在竞争条件。为了解决这个问题,我直接使用了summary的内存:

func calcSummary() {
    let group = dispatch_group_create()
    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)

    let summaryMem = UnsafeMutableBufferPointer<Int>(start: &summary, count: 10)

    for i in 0 ..< 10 {
        dispatch_group_async(group, queue, {
           let base = i * 50000
           for x in base ..< base + 50000 {
              summaryMem[i] += self.array[x]
           }
        })
    }

    dispatch_group_notify(group, queue, {
        println(self.summary)
    })
}

这是有效的(到目前为止)。

修改 Mike S在下面的评论中有一个非常好的观点。我还找到了this blog post,它解释了这个问题。

答案 3 :(得分:2)

你也可以使用concurrentPerform(iterations: Int, execute work: (Int) -> Swift.Void)(自Swift 3起)。

它有一个更简单的语法:

DispatchQueue.concurrentPerform(iterations: iterations) {i in
        performOperation(i)
}

并等待所有线程在返回之前完成。

答案 4 :(得分:0)

任何同时分配数组第i个元素的解决方案都有竞争条件的风险(Swift的数组不是线程安全的)。另一方面,在更新之前分派到同一队列(在这种情况下为main)解决了该问题,但导致整体性能降低。我看到采用这两种方法之一的唯一原因是,如果数组(摘要)无法等待所有并发操作完成。

否则,对本地副本执行并发操作,并在完成时将其分配给摘要。没有比赛条件,没有性能影响:

快捷键4

func calcSummary(of array: [Int]) -> [Int] {
    var summary = Array<Int>.init(repeating: 0, count: array.count)

    let iterations = 10 // number of parallel operations  

    DispatchQueue.concurrentPerform(iterations: iterations) { index in
        let start = index * array.count / iterations
        let end = (index + 1) * array.count / iterations

        for i in start..<end {
            // Do stuff to get the i'th element
            summary[i] = Int.random(in: 0..<array.count)
        }
    }

    return summary
}

我已经回答了类似的问题here,该问题只是在对另一个数组进行计算之后简单地初始化了一个数组