我的第一个问题!
我正在对视频源进行 CPU 密集型图像处理,我想使用 OperationQueue。然而,结果绝对是可怕的。这是一个示例 - 假设我有一个 CPU 密集型操作:
var data = [Int].init(repeating: 0, count: 1_000_000)
func run() {
let startTime = DispatchTime.now().uptimeNanoseconds
for i in data.indices { data[i] = data[i] &+ 1 }
NSLog("\(DispatchTime.now().uptimeNanoseconds - startTime)")
}
在我的笔记本电脑上执行大约需要 40 毫秒。我计时一百次:
(1...100).forEach { i in run(i) }
它们平均每个约 42 毫秒,总共约 4200 毫秒。我有 4 个物理内核,所以我尝试在 OperationQueue 上运行它:
var q = OperationQueue()
(1...100).forEach { i in
q.addOperation {
run(i)
}
}
q.waitUntilAllOperationsAreFinished()
有趣的事情发生取决于 q.maxConcurrentOperationCount:
concurrency single operation total
1 45ms 4500ms
2 100-250ms 8000ms
3 100-300ms 7200ms
4 250-450ms 9000ms
5 250-650ms 9800ms
6 600-800ms 11300ms
我使用 .background
的默认 QoS,可以看到线程优先级是默认值 (0.5)。查看 Instruments 的 CPU 利用率,我看到了很多浪费的周期(第一部分是在主线程上运行,第二部分是使用 OperationQueue 运行):
我用 C 编写了一个简单的线程队列,并在 Swift 中使用它,它随内核线性扩展,因此我的速度提高了 4 倍。但是我在 Swift 上做错了什么?
更新:我想我们已经得出结论,这是 DispatchQueue 中的一个合法错误。那么问题实际上是询问 DispatchQueue 代码中的问题的正确 channel 是什么?
最佳答案
您似乎测量了每次 run
执行的挂钟时间。这似乎不是正确的指标。并行化问题并不意味着每次运行都会执行得更快……它只是意味着您可以一次运行多次。
无论如何,让我验证你的结果。
您的函数 run
似乎只在某些时候采用参数。为了清楚起见,让我定义一个类似的函数:
func increment(_ offset : Int) {
for i in data.indices { data[i] = data[i] &+ offset }
}
在我的测试机上,在 Release模式下,此代码每次输入需要 0.68 ns 或每次添加大约需要 2.3 个周期(在 3.4 GHz 时)。禁用边界检查会有所帮助(每个条目低至 0.5 ns)。
无论如何。那么接下来让我们按照您的建议将问题并行化:
var q = OperationQueue()
for i in 1...queues {
q.addOperation {
increment(i)
}
}
q.waitUntilAllOperationsAreFinished()
这似乎不是特别安全,但速度快吗?
好吧,它更快了......我每次进入 0.3 ns。
源代码:https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/extra/swift/opqueue
关于swift - NSOperationQueue 在计算任务上的性能比单线程差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41904500/