我想知道如果循环中的条件总是被执行,如果它是一个数学运算,如:
int margin = 100;
for(int i=0; i<margin/2;i++);//is margin divided by two at every cycle?
所以我写了以下程序:
long margin = 1000000000, halfOfMargin = margin/2;
clock_t begin_time;
clock_t stop_time;
for(long j=0;j<10;++j)
{
begin_time = clock();
for(long i=0;i<margin/2;i++);
stop_time = clock ();
cout <<"With margin/2 :"<< float( stop_time - begin_time ) / CLOCKS_PER_SEC<<"\n";
}
for(long j=0;j<10;++j)
{
begin_time = clock();
for(long i=0;i<halfOfMargin;i++);
stop_time = clock ();
cout <<"With halfOfMargin :"<< float( stop_time - begin_time ) / CLOCKS_PER_SEC<<"\n";
}
分别测试这两种方法,(对不起凌乱的代码)。
我得出结论,在Debug配置中,除法版本甚至比第二个版本更快。让我感到惊讶的是,当我切换到Release时,每次都为每个版本输出0,如果实际执行了for
指令,这将是不可能的(在Debug配置中每个输出的时间)大概是一秒钟。
我的问题是,如果编译器发现它们是不必要的,那么它是否可能被编译,并且如果不是为什么这两种情况的时间如此不同呢?
注意:我使用Visual Studio 2012 express作为具有默认编译器的IDE。
答案 0 :(得分:4)
C ++标准描述了抽象机器的行为。
其行为的某些部分是外部可观察的。
可以在所谓的“as-if”规则下跳过任何其效果无法观察的行为。
这意味着编译器可以自由跳过无用的代码。在某些情况下,它甚至可以跳过一个无限循环,证明它没有任何人可以发现的变化,将状态设置为允许循环退出的状态,并退出循环。
在“调试”中,编译器往往优化得更少。
答案 1 :(得分:3)
由于'margin'变量是本地的而未被修改,编译器可以看到它永远不会改变,只会进行一次测试。
如果你调用一个函数并且正在测试结果,那么它可能不会。
尝试将volatile添加到变量定义并再次运行它。然后编译器会认为变量可以从另一个线程修改并在每个循环中执行测试。
答案 2 :(得分:2)
是的,编译器会这样做。这属于as-if rule - 只要可观察行为没有改变,代码就可以重新组织。由于循环中对i
的更改无法被任何事物观察到(即编译器无法看到函数体或IO函数的函数),因此可以加快速度。
如果你看一下clang或gcc生成的代码,它也会被优化出来:
void foo(int n)
{
int i;
for(i = 0; i < n/2; ++i);
sink(i);
}
foo(int): # @foo(int)
# BB#0:
#DEBUG_VALUE: foo:n <- %EDI
#DEBUG_VALUE: foo:i <- 0
mov eax, edi
shr eax, 31
add eax, edi
xor ecx, ecx
sar eax
cmovs eax, ecx
mov edi, eax
jmp sink(int) # TAILCALL
gcc:
foo(int):
mov eax, edi
shr eax, 31
add eax, edi
xor edi, edi
sar eax
test eax, eax
jle .L2
.L3:
add edi, 1
cmp edi, eax
jne .L3
.L2:
jmp sink(int)