优化 Swift 中的嵌套 for 循环

die*_*men 5 for-loop uiimage ios swift

我得到了这个方法来计算 a 中的白色像素UIImage,我需要遍历所有像素来增加我找到的每个白色像素的计数器。我\xc2\xb4m 试图提高它的性能,但我没有\xc2\xb4t 找到更好的方法。有任何想法吗?

\n\n
func whitePixelCount() -> Int {\n    let width = Int(image.size.width)\n    let height = Int(image.size.height)\n    var counter = 0\n    for x in 0..<(width*scale) {\n        for y in 0..<(height*scale) {\n            // We multiply per 4 because of the 4 channels, RGBA, but later we just use the Alpha\n            let pixelIndex = (width * y + x) * 4\n\n            if pointer[pixelIndex + Component.alpha.rawValue] == 255 {\n                counter += 1\n            }\n        }\n    }\n    return counter\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
    \n
  • Component.alpha.rawValue等于3
  • \n
  • scaleInt(image.scale)
  • \n
  • pointer来自:

    \n\n
    guard let cfdata = self.image.cgImage?.dataProvider?.data,\n    let pointer = CFDataGetBytePtr(cfdata) else {\n        return nil\n}\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
\n

Rob*_*Rob 5

一些观察结果:

\n\n
    \n
  1. 确保您\xe2\x80\x99 使用的是优化/发布版本,而不是未优化的调试版本。在我的设备上,调试版本大约需要 4 秒才能处理 12 兆像素的图像,而发布版本则需要 0.3 秒。

  2. \n
  3. 当您有一个for循环时,您可以对其进行并行化以利用 CPU 上的所有内核。通过使用跨步算法,for循环速度几乎快了 4 倍。

    \n\n

    听起来不错,但不幸的是,问题是在处理图像的 0.3 秒中,大部分是图像缓冲区的准备。(现在,在您的示例中,您\xe2\x80\x99没有将其重新渲染到预定义的像素缓冲区中,恕我直言,这有点危险,所以也许您没有\xe2\x80\x99t有这种开销。但是,无论如何,区别10+ 毫秒通常是观察不到的,除非您\xe2\x80\x99 正在处理数百个图像。)for循环仅占经过时间的 16 毫秒。因此,虽然将其减少到 4 毫秒几乎快了 4 倍,但从用户的角度来看,这并不重要。

  4. \n
\n\n

无论如何,请随意在我原来的答案中查看下面的跨步并行算法。

\n\n
\n\n

提高for循环性能的一种非常简单的方法是使用concurrentPerform并行化例程:

\n\n

例如,这是一个非并行例程:

\n\n
var total = 0\n\nfor x in 0..<maxX {\n    for y in 0..<maxY {\n        if ... {\n            total += 1\n        }\n    }\n}\n\nprint(total)\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以通过以下方式并行化它

\n\n
    \n
  • 翻转xy循环,因为我们希望外层循环成为图像中的一行。这个想法是为了确保每个线程不仅应该使用连续的内存块,而且我们希望最小化重叠量以避免 \xe2\x80\x9ccache 晃动\xe2\x80\x9d。因此考虑:

    \n\n
    for y in 0..<maxY {\n    for x in 0..<maxX {\n        if ... {\n            total += 1\n        }\n    }\n}\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    我们\xe2\x80\x99实际上并不打算使用上面的内容,但我们\xe2\x80\x99将在下一步中使用它作为模型;

  • \n
  • 将外for循环(现在的y坐标)替换为concurrentPerform

    \n\n
    var total = 0\n\nlet syncQueue = DispatchQueue(label: "...")\n\nDispatchQueue.concurrentPerform(iterations: maxY) { y in\n    var subTotal = 0\n    for x in 0..<maxX {\n        if ... {\n            subTotal += 1\n        }\n    }\n    syncQueue.sync {\n        total += subTotal\n    }\n}\n\nprint(total)\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
\n\n

所以,想法是:

\n\n
    \n
  • 将外for循环替换为concurrentPerform;
  • \n
  • 不是尝试total对 的每次迭代进行更新x,而是subTotal为每个线程设置一个变量,并且仅在最后更新total(最大限度地减少多个线程对此共享资源的争用);和
  • \n
  • 使用一些同步机制(我\xe2\x80\x99这里使用了串行队列,但任何同步机制都可以)进行更新total以确保线程安全。
  • \n
\n\n

我试图使示例尽可能简单,但甚至还可以进行其他优化:

\n\n
    \n
  • 不同的同步技术提供不同的性能。例如,您可以NSLock通过在协议扩展中定义一个方法(以提供一种良好、安全的使用锁的方式)来使用(传统观点认为它速度较慢,但​​我最近的基准测试表明,在许多情况下性能可能比 GCD 更好sync),例如所以:

    \n\n
    // Adapted from Apple\xe2\x80\x99s `withCriticalSection` code sample\n\nextension NSLocking {\n    func sync<T>(_ closure: () throws -> T) rethrows -> T {\n        lock()\n        defer { unlock() }\n        return try closure()\n    }\n}\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    然后你可以做类似的事情:

    \n\n
    let lock = NSLock()\n\nDispatchQueue.concurrentPerform(iterations: maxY) { y in\n    var subTotal = 0\n    for x in 0..<maxX {\n        if ... {\n            subTotal += 1\n        }\n    }\n    lock.sync {\n        total += subTotal\n    }\n}\n\nprint(total)\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    请随意尝试您想要的任何同步机制。但想法是,如果您\xe2\x80\x99 要从total多个线程访问,请确保以线程安全的方式进行。如果您想检查线程安全性,请暂时打开 \xe2\x80\x9cThread Sanitizer\xe2\x80\x9d。

  • \n
  • 如果每个线程上的工作量不够(例如,maxXxe2x80x99 不是很大,或者在本例中,算法非常快),则并行例程的开销可以开始抵消让多个核心参与计算的好处。因此,您可以在每次迭代中 \xe2\x80\x9cstride\xe2\x80\x9d 遍历多行y。例如:

    \n\n
    let lock = NSLock()\n\nlet stride = maxY / 20\nlet iterations = Int((Double(height) / Double(stride)).rounded(.up))\n\nDispatchQueue.concurrentPerform(iterations: iterations) { i in\n    var subTotal = 0\n    let range = i * stride ..< min(maxY, (i + 1) * stride)\n    for y in range {\n        for x in 0 ..< maxX {\n            if ... {\n                subTotal += 1\n            }\n        }\n    }\n\n    lock.sync { count += subTotal }\n}\n
    Run Code Online (Sandbox Code Playgroud)
  • \n
\n