die*_*men 5 for-loop uiimage ios swift
我得到了这个方法来计算 a 中的白色像素UIImage,我需要遍历所有像素来增加我找到的每个白色像素的计数器。我\xc2\xb4m 试图提高它的性能,但我没有\xc2\xb4t 找到更好的方法。有任何想法吗?
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}\nRun Code Online (Sandbox Code Playgroud)\n\nComponent.alpha.rawValue等于3scale是Int(image.scale)pointer来自:
guard let cfdata = self.image.cgImage?.dataProvider?.data,\n let pointer = CFDataGetBytePtr(cfdata) else {\n return nil\n}\nRun Code Online (Sandbox Code Playgroud)一些观察结果:
\n\n确保您\xe2\x80\x99 使用的是优化/发布版本,而不是未优化的调试版本。在我的设备上,调试版本大约需要 4 秒才能处理 12 兆像素的图像,而发布版本则需要 0.3 秒。
当您有一个for循环时,您可以对其进行并行化以利用 CPU 上的所有内核。通过使用跨步算法,for循环速度几乎快了 4 倍。
听起来不错,但不幸的是,问题是在处理图像的 0.3 秒中,大部分是图像缓冲区的准备。(现在,在您的示例中,您\xe2\x80\x99没有将其重新渲染到预定义的像素缓冲区中,恕我直言,这有点危险,所以也许您没有\xe2\x80\x99t有这种开销。但是,无论如何,区别10+ 毫秒通常是观察不到的,除非您\xe2\x80\x99 正在处理数百个图像。)for循环仅占经过时间的 16 毫秒。因此,虽然将其减少到 4 毫秒几乎快了 4 倍,但从用户的角度来看,这并不重要。
无论如何,请随意在我原来的答案中查看下面的跨步并行算法。
\n\n提高for循环性能的一种非常简单的方法是使用concurrentPerform并行化例程:
例如,这是一个非并行例程:
\n\nvar 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)\nRun Code Online (Sandbox Code Playgroud)\n\n您可以通过以下方式并行化它
\n\n翻转x和y循环,因为我们希望外层循环成为图像中的一行。这个想法是为了确保每个线程不仅应该使用连续的内存块,而且我们希望最小化重叠量以避免 \xe2\x80\x9ccache 晃动\xe2\x80\x9d。因此考虑:
for y in 0..<maxY {\n for x in 0..<maxX {\n if ... {\n total += 1\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n我们\xe2\x80\x99实际上并不打算使用上面的内容,但我们\xe2\x80\x99将在下一步中使用它作为模型;
将外for循环(现在的y坐标)替换为concurrentPerform:
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)\nRun Code Online (Sandbox Code Playgroud)所以,想法是:
\n\nfor循环替换为concurrentPerform;total对 的每次迭代进行更新x,而是subTotal为每个线程设置一个变量,并且仅在最后更新total(最大限度地减少多个线程对此共享资源的争用);和total以确保线程安全。我试图使示例尽可能简单,但甚至还可以进行其他优化:
\n\n不同的同步技术提供不同的性能。例如,您可以NSLock通过在协议扩展中定义一个方法(以提供一种良好、安全的使用锁的方式)来使用(传统观点认为它速度较慢,但我最近的基准测试表明,在许多情况下性能可能比 GCD 更好sync),例如所以:
// 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}\nRun Code Online (Sandbox Code Playgroud)\n\n然后你可以做类似的事情:
\n\nlet 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)\nRun Code Online (Sandbox Code Playgroud)\n\n请随意尝试您想要的任何同步机制。但想法是,如果您\xe2\x80\x99 要从total多个线程访问,请确保以线程安全的方式进行。如果您想检查线程安全性,请暂时打开 \xe2\x80\x9cThread Sanitizer\xe2\x80\x9d。
如果每个线程上的工作量不够(例如,maxXxe2x80x99 不是很大,或者在本例中,算法非常快),则并行例程的开销可以开始抵消让多个核心参与计算的好处。因此,您可以在每次迭代中 \xe2\x80\x9cstride\xe2\x80\x9d 遍历多行y。例如:
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}\nRun Code Online (Sandbox Code Playgroud)