ios - 在 Swift 中优化嵌套 for 循环

标签 ios swift for-loop uiimage

我得到了计算 UIImage 中白色像素的方法,我需要遍历所有像素以增加我找到的每个白色像素的计数器。我试图提高它的性能,但我没有找到更好的方法。有什么想法吗?

func whitePixelCount() -> Int {
    let width = Int(image.size.width)
    let height = Int(image.size.height)
    var counter = 0
    for x in 0..<(width*scale) {
        for y in 0..<(height*scale) {
            // We multiply per 4 because of the 4 channels, RGBA, but later we just use the Alpha
            let pixelIndex = (width * y + x) * 4

            if pointer[pixelIndex + Component.alpha.rawValue] == 255 {
                counter += 1
            }
        }
    }
    return counter
}
  • Component.alpha.rawValue 等于 3
  • scaleInt(image.scale)
  • 指针来自:

    guard let cfdata = self.image.cgImage?.dataProvider?.data,
        let pointer = CFDataGetBytePtr(cfdata) else {
            return nil
    }
    

最佳答案

一些观察:

  1. 确保您使用的是优化/发布版本,而不是未优化的调试版本。在我的设备上,调试构建需要大约 4 秒来处理 12 兆像素的图像,而发布构建需要 0.3 秒。

  2. 当您有一个 for 循环时,您可以将其并行化以利用 CPU 上的所有内核。通过跨步算法执行此操作,for 循环快了将近 4 倍。

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

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


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

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

var total = 0

for x in 0..<maxX {
    for y in 0..<maxY {
        if ... {
            total += 1
        }
    }
}

print(total)

你可以并行化它

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

    for y in 0..<maxY {
        for x in 0..<maxX {
            if ... {
                total += 1
            }
        }
    }
    

    我们实际上并不打算使用上面的内容,但我们将在下一步中将其用作模型;

  • concurrentPerform 替换外部 for 循环(现在是 y 坐标):

    var total = 0
    
    let syncQueue = DispatchQueue(label: "...")
    
    DispatchQueue.concurrentPerform(iterations: maxY) { y in
        var subTotal = 0
        for x in 0..<maxX {
            if ... {
                subTotal += 1
            }
        }
        syncQueue.sync {
            total += subTotal
        }
    }
    
    print(total)
    

所以,思路是:

  • concurrentPerform替换外层for循环;
  • 不是尝试为 x 的每次迭代更新 total,而是为每个线程设置一个 subTotal 变量,并且只更新 total 最后(最小化多个线程对该共享资源的争用);和
  • 使用一些同步机制(我在这里使用串行队列,但任何同步机制都可以)更新total以确保线程安全。

我试图让这个例子尽可能简单,但还有其他可以做的优化:

  • 不同的同步技术提供不同的性能。例如。您可以通过在协议(protocol)扩展(提供一种安全的使用锁的方式),如下所示:

    // Adapted from Apple’s `withCriticalSection` code sample
    
    extension NSLocking {
        func sync<T>(_ closure: () throws -> T) rethrows -> T {
            lock()
            defer { unlock() }
            return try closure()
        }
    }
    

    然后你可以这样做:

    let lock = NSLock()
    
    DispatchQueue.concurrentPerform(iterations: maxY) { y in
        var subTotal = 0
        for x in 0..<maxX {
            if ... {
                subTotal += 1
            }
        }
        lock.sync {
            total += subTotal
        }
    }
    
    print(total)
    

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

  • 如果每个线程上没有足够的工作(例如 maxX 不是很大,或者在这种情况下,算法非常快),并行化例程的开销可以开始抵消让多个核心参与计算的好处。因此,您可以在每次迭代中“跨越”多行 y。例如:

    let lock = NSLock()
    
    let stride = maxY / 20
    let iterations = Int((Double(height) / Double(stride)).rounded(.up))
    
    DispatchQueue.concurrentPerform(iterations: iterations) { i in
        var subTotal = 0
        let range = i * stride ..< min(maxY, (i + 1) * stride)
        for y in range {
            for x in 0 ..< maxX {
                if ... {
                    subTotal += 1
                }
            }
        }
    
        lock.sync { count += subTotal }
    }
    

关于ios - 在 Swift 中优化嵌套 for 循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58511590/

相关文章:

ios - 在 swift 中拖放 collectionViewCell

ios - 应用程序关闭时的 iBeacon 通知

ios - 我需要帮助“使用文本字段自动完成 google places( objective-c )

ios - Apple Mach-O 链接器和同上错误 - Xcode 8

swift - 如何从字典中制作 Decodable 对象?

javascript - js 中的 for 循环,带有 starter 函数

c++ - 在 for 的括号中定义变量

objective-c - for 循环在editingStyleForRowAtIndexPath 中仅执行一次

ios - 在构建阶段编辑 app.entitlements 文件

iphone - 应用程序关闭时如何处理推送负载?