NSOperationQueue在计算任务

时间:2017-01-27 23:09:12

标签: swift multithreading macos performance

我的第一个问题!

我正在对视频Feed进行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下运行):

CPU Utilization

我在C中编写了一个简单的线程队列,并使用了Swift中的线程队列,它与内核线性扩展,因此我可以将速度提高4倍。但是我对Swift的错误是什么?

更新:我认为我们已经得出结论,这是DispatchQueue中的合法错误。那么问题实际上是在DispatchQueue代码中询问问题的正确渠道是什么?

2 个答案:

答案 0 :(得分:0)

var xml = @"<COMPARABLE_SALE PropertySequenceIdentifier=""3"" ProjectName=""Villages of Devinshire"" ProjectPhaseIdentifier=""1"" PropertySalesAmount=""132500"" SalesPricePerGrossLivingAreaAmount=""109.32"" DataSourceDescription=""FMLS, 5559496;DOM 80"" DataSourceVerificationDescription=""Tax Recs/2ndGen/Deeds"" SalesPriceTotalAdjustmentPositiveIndicator=""N"" SalePriceTotalAdjustmentAmount=""-1500"" SalesPriceTotalAdjustmentGrossPercent=""1.1"" SalePriceTotalAdjustmentNetPercent=""1.1"" AdjustedSalesPriceAmount=""131000""> <SALE_PRICE_ADJUSTMENT _Type=""GrossBuildingArea"" _Description=""1,254""/> <SALE_PRICE_ADJUSTMENT _Type=""BasementArea"" _Description=""1,254 Sq.Ft.""/> <SALE_PRICE_ADJUSTMENT _Type=""BasementFinish"" _Description=""1rr2ba4o""/> </COMPARABLE_SALE>"; var xDoc = XDocument.Parse(xml); var description = xDoc.Root.Elements("SALE_PRICE_ADJUSTMENT") .First(e => e.Attribute("_Type").Value == "GrossBuildingArea") .Attribute("_Description") .Value; 将运行优先级最低的线程。如果您正在寻找快速执行,请考虑.background并确保在开启编译器优化时测量性能。

还要考虑使用DispatchQueue而不是OperationQueue。它可能具有更少的开销和更好的性能。

根据您的评论更新:试试这个。它从我的笔记本电脑上的38秒到14左右。

值得注意的变化:

  • 我明确并发了队列
  • 我在发布模式下运行
  • 用随机数替换内循环计算,原来得到优化
  • QoS设置为更高级别:QoS现在按预期工作,.userInitiated永远运行

var data = [Int] .init(重复:0,计数:1_000_000)

.background

但仍有问题 - 串行队列运行速度提高3倍,即使它不使用所有核心。

答案 1 :(得分:0)

您似乎衡量每个run执行的挂钟时间。这似乎不是正确的指标。并行化问题并不意味着每次运行都会执行得更快...它只是意味着您可以一次执行多次运行。

无论如何,让我验证你的结果。

您的函数run似乎只在某些时候使用参数。让我明确定义一个类似的功能:

func increment(_ offset : Int) {
  for i in data.indices { data[i] = data[i] &+ offset }
}

在我的测试机器上,在释放模式下,此代码每次输入需要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