【问题标题】:What is the absolutely fastest for loop in c?c语言中最快的for循环是什么?
【发布时间】:2011-01-21 13:16:46
【问题描述】:

我正在尝试编写用于访问图像像素的优化代码,并且需要在不下降到汇编级别的情况下超快地创建一个 for 循环。此外,索引是沿行完成的,以最大限度地减少缓存未命中。

这就是我所拥有的:

for (indr=0;indr<(height-1)*width;indr+=width) {
        for (indc=0;indc<width;indc++){
            I[indr+indc]= dostuff ;
        }
    }

我不能让它成为一个循环,因为“dostuff”包括访问不在同一行上的元素。

有更快的方法吗?

编辑 好的,因为我之前的帖子有点不清楚,我在这里添加了完整的代码。它非常难以理解,但总体思路是我使用积分图像对一个简单的框进行卷积。图像首先在左侧和底部填充 ws+1 个零,在右侧和顶部填充 ws 零。然后将其制成积分图像Ii。以下函数采用积分图像并提取结果 Ic 与原始图像大小相同的卷积。

void convI(float *Ic,float *Ii,int ws, int width, int height)
{
    int W=width+ws*2+1,indR;
    int H=height+ws*2+1,indC;
    int w=width, indr;
    int h=height, indc;
    int jmpA=W*(ws+1),jmpC=W*ws,jmpB=ws+1,jmpD=ws;

    for (indR=W*(ws+1),indr=0;indr<width*(height-1);indR+=W,indr+=width) {
        for (indC=ws+1,indc=0;indc<width;indC++,indc++){
            //Performs I[indA]+I[indD]-I[indB]-I[indC];
            Ic[indr+indc]=
            Ii[indR-jmpA+indC-jmpB]+
            Ii[indR+jmpC+indC+jmpD]-
            Ii[indR+jmpC+indC-jmpB]-
            Ii[indR-jmpA+indC+jmpD];
        }
    }
}

这就是“dostuff”部分。循环很慢。

【问题讨论】:

  • 循环总是比访问内存快。显示您的“dostuff”代码,或告诉我们它读取的内存。

标签: c loops for-loop


【解决方案1】:

如果您启用了所有优化级别,则没有太多理由表明其他代码会比您提供的代码具有更好的性能。

您为什么怀疑循环本身是一个瓶颈?在不知道自己实际在做什么的情况下,没有什么可以说的。对您的代码进行基准测试,如果您有疑问,请查看它生成的汇编程序。

编辑:在显示循环的内部之后。

将索引计算的表达式尽可能多地放在循环之外是有一点潜力的。由于它与循环变量混合在一起,因此可能无法按原样进行优化。 (或者只是重新排序索引的计算,以便编译器可以看到它并可能尽可能多地预先计算。)

性能问题最有可能来自对向量的访问。如果您设法更好地计算索引,这也可能会有所改善,因为编译器/系统实际上会看到您以常规模式访问向量。

如果这没有帮助,请重新组织循环,使向量的负载是增量的,而不是存储。加载总是必须等到数据存在才能执行操作,存储对此不太敏感。

【讨论】:

    【解决方案2】:

    您可以展开最里面的循环。您将失去可读性,但 CPU 的缓存和预取队列会做得更好。虽然这总是正确的,但我不知道你会获得多少速度。 您可以将indcindr 都声明为寄存器变量,并尽量避免重新计算(height-1)*width,而是将其保存在临时变量中。你知道,乘法会占用很多时钟周期...

    【讨论】:

      【解决方案3】:

      除非您想使用像 SSE 这样的矢量化指令,否则没有什么可以做的。

      【讨论】:

      【解决方案4】:

      你所拥有的看起来不错。如果你想避免进入汇编,最好保持简单的循环简单。 GCC 很聪明。如果你很清楚你想让你的代码做什么,那么它通常可以很好地优化它。但是,如果您使用生产代码中不常见的花哨技巧,则可能难以推断出您“真正的意思”。

      根据 dostuff 的实际作用,您可能会发现在临时缓存 I[indr+indc] 中会有所收获,因此您的代码看起来像...

      char t = I[indr+indc];
      // do stuff
      I[indr+indc] = t;
      

      这段代码的性能不会变差(我假设你至少开启了基本的优化),但如果你的do stuff足够漂亮,它可能会表现得更好(如果你愿意,我可以详细说明)。

      并且不要听其他人将简单的数学从循环中解脱出来。真的没必要。如果您查看在 -O1 处生成的程序集,您会发现每次都是为您完成的。这是成本最低的优化之一。

      【讨论】:

        【解决方案5】:

        将外部循环中的height-1 提升到循环之前的分配可能会取得胜利。但是,然后,我怀疑现在的普通编译器会将其作为标准优化来执行。也可能是有另一个指针,设置为 I[indr],然后索引该 可能是一个小胜利。

        这两个都需要一些非常仔细的基准测试来记录。

        【讨论】:

          【解决方案6】:
          // DragonLord style:
          float *ic_p = I + (width * height) - 1;  // fencepost  
          // Start at the end, and work backwards 
          // assumes I is 0-based and wraps, is contiguous
          
          for (indr=(height -1) * width; indr>=0; indr-=width ) {
          // Sadly cannot test on indr -= width here
          // as the 0 pass is needed for the loop
                  for (indc=width; indc--; ){
                  // Testing on postdecrement
                  // allows you to use the 0 value one last time before testing it FTW
                      // indr and indc are both 0-based inside the loop for you
                      // e.g. indc varies from (width-1) down to 0
                      // due to postdecrement before usage
                      printf( "I[ %d + %d ] == %f \n", indr, indc, *ic_p );
                      // always use pointers in C/C++ for speed, we are not Java
                      *ic_p-- = dostuff ;
                  }
              }
          

          如果您不需要在循环中使用 indr,则可以通过从高度倒数到 0 来略微提高性能,或者如果您可以使用基于 1 的 indc,则使用预递减而不是后递减 indc,在这种情况下为 indc应该在 (width +1) 处初始化:

             for (indc=(width+1); --indc; ){
          

          【讨论】:

            猜你喜欢
            • 2020-10-01
            • 2010-09-21
            • 2017-06-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-01-02
            • 2021-03-06
            • 2012-01-05
            相关资源
            最近更新 更多