iOS 应用程序中的性能优化关键代码

Posted

技术标签:

【中文标题】iOS 应用程序中的性能优化关键代码【英文标题】:performance optimizing critical code in iOS app 【发布时间】:2013-04-04 16:54:36 【问题描述】:

我正在尝试优化我的应用的关键部分的性能。用 C 语言编写的代码循环遍历 sourceImage 的所有像素并计算到其每个邻居的“颜色距离”,决定是否记录从 colorDistance 派生的值,然后再移动到下一个邻居。

在 XCode 中对应用程序进行检测显示,70% 的时间花在看似简单的浮点计算上——比具有三个 powf 和一个 sqrtf 的代码行长七倍(colorDistance 的计算消耗 10.8%) .

在下面一些代码行的左侧,您将看到从 XCode Instruments 复制所花费的时间百分比。 (我还注意到其他平凡的代码行令人惊讶地具有相对较高的百分比,即使与我上面提到的那些不接近)。

任何关于在何处以及如何优化的提示将不胜感激。

干杯

     for (int row = 1; row < height - 1; row++)
                    
            for (int col = 1; col < width - 1; col++)
            
                int pixelIndex = (col + row * width);
1.7%            int pixelIndexIntoImage = pixelIndex * COMPONENTS_PER_PIXEL;

                // loop over pixel's 8 neighbours clockwise starting from neighbor id 0
                // using Nx[] and Ny[] as guides to calculate neighbour locations
1.6%            for (int n = 0; n < 8; n++)
                
5.3%                int neighborIndex = pixelIndex + Nx[n] + width * Ny[n];
                    int neighborIndexIntoImage = neighborIndex * COMPONENTS_PER_PIXEL;


                    // skip neighbors that are not a foreground or background
3.3%                uint8_t labelValue = labelsMap[neighborIndex];
1.1%                if (labelValue == LABEL_UNKNOWN_VALUE)
                        continue; 



                    // "color distance" between the pixel and the current neighbour
                    float colorDistance;

1.4%                if(numColorComponents == 3)
                    
5.3%                    uint8_t redPixel = sourceImage[pixelIndexIntoImage  ];
                        uint8_t grnPixel = sourceImage[pixelIndexIntoImage+1];
                        uint8_t bluPixel = sourceImage[pixelIndexIntoImage+2];

                        uint8_t redNeigh = sourceImage[neighborIndexIntoImage  ];
                        uint8_t grnNeigh = sourceImage[neighborIndexIntoImage+1];
                        uint8_t bluNeigh = sourceImage[neighborIndexIntoImage+2];

10.8%                   colorDistance = sqrtf( powf(redPixel-redNeigh, 2) + 
                                               powf(grnPixel-grnNeigh, 2) + 
                                               powf(bluPixel-bluNeigh, 2));
                    
                    else
                    
                        uint8_t pixel = sourceImage[pixelIndexIntoImage   ];
                        uint8_t neigh = sourceImage[neighborIndexIntoImage];

                        colorDistance = fabsf(pixel - neigh); 
                    

71.2%               float attackForce = 1.0 - (colorDistance / MAX_COLOR_DISTANCE);

                    if (attackForce * strengthMap[neighborIndex] > revisedStrengthMap[pixelIndex])
                    
                        //attack succeeds

                        strengthMap[pixelIndex] = attackForce * revisedStrengthMap[neighborIndex];

                        outputMask[pixelIndex] = labelsMap[neighborIndex];

                        isConverged = false; // keep iterating

                    
                

            

        

变量的定义

uint8_t *sourceImage; // 4 bytes per pixel
uint8_t *labelsMap, *outputMask; // 1 byte per pixel
int     numPixels = width * height;
float   *strengthMap        = (float*) malloc(sizeof(float)*numPixels);
float   *revisedStrengthMap = (float*) malloc(sizeof(float)*numPixels);
short   Nx[] = -1,  0,  1, 1, 1, 0, -1, -1; 
short   Ny[] = -1, -1, -1, 0, 1, 1,  1,  0; 

根据我收到的建议(乘法比除法“便宜”),我修改了一行代码,有趣的是,71.2% 下降到 1.7%,但是下面的“if”语句飙升到 64.8%——我只是不明白!

1.7%               float attackForce = 1.0 - (colorDistance * MAX_COLOR_DISTANCE_INV);

64.8%              if (attackForce * strengthMap[neighborIndex] > revisedStrengthMap[pixelIndex])

【问题讨论】:

我的理解是除法很昂贵。如果该因子相对恒定,则可以乘以倒数。另外,为什么不使用 x*x 而不是 powf(x,2)?尽管编译器可能会为您解决这个问题。 把除法变成乘法。 Ny 可能是一个 int 数组,其中包含已乘以的“宽度”。 避免内部循环中的条件。不过,我认为这在 ARM 上并不重要。 还有几个问题 - 你在这里编译完全优化了吗?如果没有,你的号码就是垃圾,应该重新收集。如果是这样,然后发布上述函数的汇编程序,我们可以更深入地了解为什么它运行这么慢。 您也可以使用一个小的查找表来查找相邻像素的偏移量。 -(width+1), -width, -(width-1), -1, 1, +(width-1), +width, +(width+1) 【参考方案1】:
           const MAX_COLOR_DISTANCE_RSP = 1 / MAX_COLOR_DISTANCE;
           float attackForce = 1.0 - (colorDistance * MAX_COLOR_DISTANCE_RSP);

另外:Neon Intrinsics 用于高速 sqrt 和 recip 估计,可以根据需要更准确。这将替换您的距离 sqrt。最后,不要使用powf,使用val * val,因为编译器可能不会将该函数优化为一个简单的mul。

您还可以通过一次读取来读取整个像素(假设 32 位对齐,这应该是 RGBA 文件格式的情况):

uint32_t *sourceImage = (uint32_t *)(&sourceImage[pixelIndexIntoImage]);
uint8_t pixels[4];
*(uint32_t *)(&pixels[0]) = *sourceImage;

现在您的像素数组已准备好读取所有 4 个组件,但由于字节序问题,您必须稍微尝试一下以确定哪个像素具有哪种颜色。一次 32 位读取比 3、8 位读取快得多。

此外,所有这些全局访问都可能会损害您的缓存。尝试将它们全部放在一个结构中以确保它们是相邻的。它还将帮助编译器进行本地池管理。

【讨论】:

这是我喜欢玩的那种东西。我什至在我目前的工作中获得报酬,这是双倍奖金:) MichaelDorgan @nielsbot,用乘法代替除法确实有点帮助——谢谢。 @MichaelDorgan,我不完全确定我理解你所说的“......将它们全部放在一个结构中”的意思,你能详细说明一下吗?另外,我正在尝试组装。干杯 我假设你所有的定义变量都是全局的。现在重读一遍,可能不是这样。技巧在于每个单独的全局都需要一些胶水代码。将它们全部放入一个结构中会将其降至 1 值并减轻寄存器溢出压力。【参考方案2】:

1.0 转换为1.0f 并确保MAX_COLOR_DISTANCE 被定义为&lt;something&gt;.0f,否则在您极其昂贵的线路上会有很多隐式类型转换。

你正在做的那种划分并不是特别昂贵;在 ARM 上,昂贵的是整数除法,因为——不管你信不信——在 ARMv7s 指令集之前没有内置的整数除法。浮点除法要快得多,至少如果您坚持单精度的话。

您是否有任何额外的限制没有提及?我注意到您的颜色距离公式与人类视觉感知颜色的方式并不真正相关。

ios 上,至少从 5 开始,它也是一种将其踢出到 GPU 的选项,因为您可以直接访问纹理缓冲区,从而消除在 OpenGL 之间来回传递数据的成本。这是一个选项吗?

【讨论】:

感谢“f”小费,我不敢相信总处理时间减少了 25%。颜色距离作为一种数学方法来实现算法的目标,它确实与人类视觉感知的颜色无关。然而,如果我能加快密集的内循环,那将是一场胜利。 这是一个很棒的技巧,我应该看到它:)【参考方案3】:

如果周期确实用于计算 attackForce,您可以预先计算一个表格,将 colorDistance 值映射到 attackForce 值,并用量化操作和查找替换除法。

【讨论】:

【参考方案4】:

乘法:

int pixelIndex = (col + row * width);
int pixelIndexIntoImage = pixelIndex * COMPONENTS_PER_PIXEL;

可以改成加法。这在使用索引时几乎适用于任何地方。

方法调用:

colorDistance = sqrtf( powf(redPixel-redNeigh, 2) + 
                                           powf(grnPixel-grnNeigh, 2) + 
                                           powf(bluPixel-bluNeigh, 2));

不要在这里使用powf。您可以简单地使用(grnPixel-grnNeigh)*(grnPixel-grnNeigh) 它仍然会更快。为什么在参数是整数时使用浮点数?

【讨论】:

编译器应自行将powf(x,2)优化为x*x 不是我见过的,但我使用的许多编译器都不是“好”:) @StephenCanon 有些人不会,甚至取决于编译器参数。然而,使用powf 的事实意味着我们正在将整数转换为浮点数。直到sqrtf.

以上是关于iOS 应用程序中的性能优化关键代码的主要内容,如果未能解决你的问题,请参考以下文章

浏览器性能优化

iOS 程序性能优化

微信小程序性能优化技巧

微信小程序性能优化技巧

.NET性能优化-使用SourceGenerator-Logger记录日志

Oracle优化