考虑这样的例子:
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
循环内的重复测试。如果是这样,在什么条件下这可能?
答案 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)
我会谨慎地说它会。是否可以保证该值或其他线程不会修改该值?
也就是说,代码的第二个版本通常更具可读性,它可能是在代码块中进行优化的最后一个版本。