- 短版 -
并行化一种算法的最佳方法是什么,该算法在Swift中对大型数组的元素进行成对比较,以便利用多个内核?
- 长版 -
我正在开发一种算法,该算法在(大)数组的元素之间执行成对比较,并从数组中删除不满足给定条件的元素。一旦元素被丢弃,我们就不需要考虑它进行进一步的比较。 这是一个简单的二次算法,其标准实现可能如下所示:
func nonParallelComputation(dataSet: [Point]) -> [Point] {
var discarded = [Bool](repeating: false, count: dataSet.count)
for i in 0..<dataSet.count {
if discarded[i] { continue }
let p1 = dataSet[i] // first point of the comparison
// try all possible second points
for j in 0..<dataSet.count {
if i == j || discarded[j] { continue }
let p2 = dataSet[j] // second point of the comparison
discarded[j] = p1.betterThan(point: p2)
}
}
// filtering the dataset
return dataSet.enumerated().filter { !discarded[$0.offset] }.map { $0.element }
}
不要过分关注实施的细节,无论如何都要大大简化。核心是discarded
的{{1}}数组,用于标记要删除的数组元素。
我以为我可能会利用我的Mac的8个内核并尝试尽可能地并行化执行。例如,我可以将数组拆分为8个块,执行块的元素与数组中所有其他元素之间的比较,并且并行运行这些比较(例如,通过使用Bool
),关心同步对共享DispatchQueue.concurrentPerform
数组的访问(这里我使用了锁定队列)。
以上代码的天真并行版本如下所示:
discarded
这种方法不能很好地工作,因为显然,锁定阵列需要花费太多时间,因此在并行版本中性能会急剧恶化。
然后我想到不仅要在块中拆分数组,而且还要限制与给定块的元素与另一个给定块的元素的比较,这样在比较元素对时几乎不需要锁。基本上,我做了8次迭代。在第一次迭代中,我将块1的元素与块1的元素进行比较,并且并行地将块2的元素与块2的元素进行比较,并且并行地,...直到块8的元素与在第二次迭代中,我将块1的元素与块2的元素进行比较,并且并行地将块2的元素与块3的元素进行比较,等等。在每次迭代结束时,我更新了func parallelComputation(dataSet: [Point]) -> [Point] {
var discarded = [Bool](repeating: false, count: dataSet.count)
let numberOfParallelThreads = 8
let lockQueue = DispatchQueue(label: "myQueue")
DispatchQueue.concurrentPerform(iterations: numberOfParallelThreads) { iteration in
let stride = Int(ceil(Double(dataSet.count) / Double(numberOfParallelThreads)))
let firstIndex = iteration * stride
let lastIndex = min((iteration + 1) * stride, dataSet.count)
guard firstIndex < lastIndex else { return }
for i in firstIndex..<lastIndex {
var firstPointDiscardedCheck = false
lockQueue.sync() {
firstPointDiscardedCheck = discarded[i]
}
if firstPointDiscardedCheck { continue }
let p1 = dataSet[i] // first point of the comparison
// try all possible second points
for j in 0..<dataSet.count {
let p2 = dataSet[j] // second point of the comparison
var secondPointDiscardedCheck = false
lockQueue.sync() {
secondPointDiscardedCheck = discarded[j]
}
if i == j || secondPointDiscardedCheck { continue }
if p1.betterThan(point: p2) {
lockQueue.sync() {
discarded[j] = true
}
}
}
}
}
// filtering the dataset
return dataSet.enumerated().filter { !discarded[$0.offset] }.map { $0.element }
}
数组。
这在性能方面具有预期的好处(仅适用于较大的阵列),但似乎过于复杂(参见下面的代码)。
编辑:我在此处添加了代码,但我对此解决方案不满意
discarded
虽然对于足够大的阵列而言比非并行解决方案快3-4倍,但该解决方案至少存在两个问题:1)它看起来过于复杂,2)它硬编码所需数量的并行线程。 / p>
必须有更好(更简单)的方式。
有什么想法吗?