如何优化C for循环?

Nir*_*Nir 15 c compiler-construction optimization

我的代码中的瓶颈部分存在性能问题.基本上它是一个简单的嵌套循环.

对问题进行概要分析表明,该程序花费了大量时间来增加循环计数器(++)和终止测试(i/j <8).

观察汇编输出我发现两个计数器都没有得到寄存器,访问它们需要花费很多周期.使用"register"关键字并不能说服编译器将它们实际放入寄存器中.是否可以采取一些措施来优化计数器的访问时间?

这是装配输出.C源只是一个带有i/j计数器的简单嵌套循环.

  2738  0.2479  2459  0.1707   :    1e6c:   jne    1dd1 <process_hooks+0x121>
  1041  0.0942  1120  0.0778   :    1e72:   addl   $0x1,0xffffffd4(%ebp)
  2130  0.1928  2102  0.1459   :    1e76:   cmpl   $0x8,0xffffffd4(%ebp)
  2654  0.2403  2337  0.1622   :    1e7a:   jne    1da0 <process_hooks+0xf0>
  809   0.0732   814  0.0565   :    1e80:   jmp    1ce2 <process_hooks+0x32>
Run Code Online (Sandbox Code Playgroud)

根据要求,这里也是C代码.编译器是gcc btw:

for (byte_index=0; byte_index < MASK_SIZE / NBBY; byte_index++)
{
    if (check_byte(mask,byte_index))
    {
        for (bit_index=0; bit_index < NBBY; bit_index++)
        {
            condition_index = byte_index*NBBY + bit_index;
            if (check_bit(condition_mask,condition_index))
            {
                .
                .
                .
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢

Pau*_*gar 13

有两个可能的原因它没有放入寄存器:

变量需要保存在内存中

如果您正在获取变量的地址或声明它变化,则不会将其保存在寄存器中.它看起来不像你那样做,但它可能会发生在该...部分.

gcc在寄存器分配方面做得不好.

这很有可能.gcc似乎有一个糟糕的分配器(基于其开发人员的评论).此外,注册分配是变化无常且难以推理的.您可能可以使用寄存器分配器优化来调整它以获得一些好处.如果您愿意,可以仅为该功能设置它们.

gcc 4.4有一个新的寄存器分配器,应该更好,但也允许你选择分配算法.这将提供额外的调整内容.

您也可以尝试使用hot属性告诉gcc更加努力.

最后,您还可以使用gcc的--param标志来调整内容.它们暴露了内部编译器设置,所以这可能不应该轻易开始.


Las*_*lan 8

当在循环计数器中获得性能瓶颈时,您应该考虑展开循环.

编辑:一如既往,在优化时,请确保您进行基准测试并说服自己获得所需的结果.

  • 如果*可能*导致循环不适合缓存,那么它*可能*对性能不利.,实际上是糟糕的''是夸大其词.优化前测量. (3认同)
  • 由于指针别名,C编译器有时无法像应该的那样展开.这意味着一些手工工作有时会产生影响. (2认同)

Toa*_*oad 6

使用intel编译器时得到的最佳结果(速度方面).

你是对的,'register'关键字只是作为编译器的提示(就像内联一样).

如果你真的认为这个循环是一个主要的瓶颈,那就输入原始组件.我知道它几乎不便携,但是再次,通常这并不重要,如果它应该是便携的......它只在一个特定的地方.

您甚至可以使用原始C代码#ifdef整个位以保持可移植性


Dan*_*son 6

    for (bit_index=0; bit_index < NBBY; bit_index++)
    {
        condition_index = byte_index*NBBY + bit_index;
        if (check_bit(condition_mask,condition_index))
        {
            .
            .
            .
        }
    }
Run Code Online (Sandbox Code Playgroud)

可能很容易;

    condition_index = byte_index * NBBY;
    for (bit_index=0; bit_index < NBBY; bit_index++, condition_index++)
    {
        if (check_bit(condition_mask,condition_index))
        {
            .
            .
            .
        }
    }
Run Code Online (Sandbox Code Playgroud)

我喜欢将计算保持在正确的范围内.你在外部循环中拥有了这个的所有信息,但是选择在内部循环中进行.新循环稍微有点乱,但这可以避免,现在你的编译器更有可能正确地做事.(它可能以前做过,但是如果不检查组件就不能确定.)

说到正确的范围,没有理由在循环之外声明循环计数器.这种C风格已经过时多年了,虽然它可能不是特别的性能劣势,但是将事物限制在最小的逻辑范围内会导致更清晰,更易于维护的代码.

对于8位,您可能会展开,但根据您的硬件,它可能无法正常工作.还有很多其他方法可以做到这一点,我可能错过了一些看着它.在硬件中,我在循环中使用条件通常对性能有害,但我没有看到任何明显的方法来避免它.我当然会考虑在外部循环中迭代位而不是字节,以避免在常见情况下的乘法.只是建议这个...我想在这种情况下不会有明显的优势.


jco*_*der 5

您需要确定这是一个瓶颈,在现代处理器上,指令被拆开,部分指令不按顺序执行,并且使用缓存和旁视缓冲区,这完全有可能不会慢.