C ++编译器可以优化“for”循环中的“if”语句吗?

时间:2009-09-22 21:25:36

标签: c++ optimization compiler-construction

考虑这样的例子:

if (flag)
  for (condition)
    do_something();
else
  for (condition)
    do_something_else();

如果flag循环中的for没有变化,那么这在语义上应该等同于:

for (condition)
  if (flag)
    do_something();
  else
    do_something_else();

仅在第一种情况下,代码可能会更长(例如,如果使用了多个for循环,或者do_something()是一个与do_something_else()大致相同的代码块,而在第二种情况下,标志会被多次检查。

我很好奇当前的C ++编译器(最重要的是,g ++)是否能够优化第二个示例以摆脱for循环内的重复测试。如果是这样,在什么条件下这可能?

7 个答案:

答案 0 :(得分:19)

是的,如果确定标志没有改变且do_something或do_something_else无法更改,则可以将其拉出循环。我听说这个称为循环提升,但维基百科有entry称为“循环不变代码运动”。

如果flags是局部变量,编译器应该能够进行此优化,因为它保证不会对生成的代码的行为产生影响。

如果flags是一个全局变量,并且你在循环中调用函数它可能不会执行优化 - 它可能无法确定这些函数是否修改了全局变量。

这也可能受到您所做的优化的影响 - 优化尺寸会有利于非提升版本,而优化速度可能会有利于提升版本。

一般情况下,这不是你应该担心的事情,除非分析告诉你该函数是一个热点,并且你看到通过遍历程序集编译输出实际生成的代码效率低于。像这样的微优化你应该总是留给编译器,除非你绝对必须。

答案 1 :(得分:17)

尝试使用GCC和-O3:

void foo();
void bar();

int main()
{
    bool doesnt_change = true;
    for (int i = 0; i != 3; ++i) {
        if (doesnt_change) {
            foo();
        }
        else {
            bar();
        }
    }
}

主要结果:

_main:
pushl   %ebp
movl    %esp, %ebp
andl    $-16, %esp
call    ___main
call    __Z3foov
call    __Z3foov
call    __Z3foov
xorl    %eax, %eax
leave
ret

因此它确实优化了选择(并展开较小的循环)。

如果doesnt_change是全局的,则不会进行此优化。

答案 2 :(得分:1)

我确定编译器是否可以确定该标志是否保持不变,它可以进行一些改组:

const bool flag = /* ... */;
for (..;..;..;)
{
    if (flag)
    {
        // ...
    }
    else
    {
        // ...
    }
}

如果flag不是const,编译器不一定能优化循环,因为它不能确定flag不会改变。我认为,如果它进行静态分析,但并非所有编译器都能进行静态分析。 const是告诉编译器标志不会改变的可靠方法,之后由编译器决定。

像往常一样,查看并确定它是否真的存在问题。

答案 3 :(得分:1)

一般来说,是的。但是没有保证,编译器可以做到的地方可能很少见。

大多数编译器没有问题的做法是将不可变的评估从循环中提升,例如:如果你的情况是

if (a<b) ....

当a和b不受循环影响时,将在循环之前进行一次比较。

这意味着如果编译器可以确定条件没有改变,那么测试便宜且跳跃预测。这反过来意味着测试本身需要一个周期或根本没有周期(真的)。

在哪些情况下分割循环会有益吗?

a)非常紧凑的循环,其中1个循环是显着的成本
b)包含两个部分的整个循环不适合代码缓存

现在,编译器只能对代码缓存做出假设,并且通常可以按照一个分支适合缓存的方式对代码进行排序。

没有任何测试,我认为a)是应用这种优化的唯一情况,因为它总是更好的选择:

在哪种情况下拆分循环会不好?

当分割循环增加代码大小超出代码缓存时,您将受到重大影响。现在,只有在另一个循环中调用循环本身时才会影响你,但这是编译器通常无法确定的。

<强> [编辑]
我无法让VC9拆分以下循环(少数可能实际上有益的情况之一)

extern volatile int vflag = 0;

int foo(int count)
{
   int sum = 0;
   int flag = vflag;
   for(int i=0; i<count; ++i)
   {
      if (flag)
         sum += i;
      else
         sum -= i;
   }

   return sum;
}

[编辑2]
请注意,对于int flag = true;,第二个分支确实会被优化掉。 (并且不,const在这里没有区别;))

这是什么意思?要么它不支持,那没关系,我的分析是错误的;-)

一般来说,我认为这是一种仅在极少数情况下有价值的优化,并且可以在大多数情况下轻松完成。

答案 4 :(得分:1)

正如许多人所说:这取决于。

如果你想确定,你应该尝试强制编译时决定。模板通常会派上用场:

for (condition)
  do_it<flag>();

答案 5 :(得分:0)

它被称为循环不变,优化称为循环不变代码运动以及代码提升。它在条件中的事实肯定会使代码分析变得更复杂,编译器可能会或可能不会反转循环和条件,具体取决于优化器的巧妙程度。

对于此类问题的任何特定情况都有一般性答案,那就是编译程序并查看生成的代码。

答案 6 :(得分:0)

我会谨慎地说它会。是否可以保证该值或其他线程不会修改该值?

也就是说,代码的第二个版本通常更具可读性,它可能是在代码块中进行优化的最后一个版本。