甚至在 C++ 中执行空循环吗?
Are empty for loops even executed in C++?
我想知道如果 for 循环中的条件是数学运算,是否总是执行:
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";
}
分别测试两种方法,(代码乱七八糟请见谅)
我得出结论,在调试配置中,划分版本甚至比第二种方法更快。令我惊讶的是,当我切换到 Release 时,我每次都得到每个版本的输出 0,如果实际执行 for
指令,这是不可能的(在 Debug 配置中,每次输出的时间大约一秒半)。
我的问题是,如果编译器发现不需要的指令,是否有可能跳过它们,如果不是,为什么这两种情况的时间如此不同?
注意:我使用 Visual Studio 2012 express 作为默认编译器的 IDE。
C++ 标准描述了抽象机的行为。
其行为的某些部分可以从外部观察到。
根据所谓的 "as-if" 规则,可以跳过其影响不可观察的任何行为。
这意味着编译器可以自由地跳过无用的代码。在某些情况下,它甚至可以通过证明它没有改变任何人可以发现的任何东西来跳过无限循环,将状态设置为允许循环退出的状态,然后退出循环。
在 "debug" 中,编译器倾向于优化得更少。
是的,编译器会这样做。这属于 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)
由于'margin'变量是局部的,没有被修改,编译器可以看到它永远不会改变,只会做一次测试。
如果您调用一个函数并正在测试结果,那么它可能不能。
尝试将 volatile 添加到变量定义中,然后再次 运行。然后编译器会认为变量可以从另一个线程修改并在每个循环中执行测试。
我想知道如果 for 循环中的条件是数学运算,是否总是执行:
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";
}
分别测试两种方法,(代码乱七八糟请见谅)
我得出结论,在调试配置中,划分版本甚至比第二种方法更快。令我惊讶的是,当我切换到 Release 时,我每次都得到每个版本的输出 0,如果实际执行 for
指令,这是不可能的(在 Debug 配置中,每次输出的时间大约一秒半)。
我的问题是,如果编译器发现不需要的指令,是否有可能跳过它们,如果不是,为什么这两种情况的时间如此不同?
注意:我使用 Visual Studio 2012 express 作为默认编译器的 IDE。
C++ 标准描述了抽象机的行为。
其行为的某些部分可以从外部观察到。
根据所谓的 "as-if" 规则,可以跳过其影响不可观察的任何行为。
这意味着编译器可以自由地跳过无用的代码。在某些情况下,它甚至可以通过证明它没有改变任何人可以发现的任何东西来跳过无限循环,将状态设置为允许循环退出的状态,然后退出循环。
在 "debug" 中,编译器倾向于优化得更少。
是的,编译器会这样做。这属于 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)
由于'margin'变量是局部的,没有被修改,编译器可以看到它永远不会改变,只会做一次测试。
如果您调用一个函数并正在测试结果,那么它可能不能。
尝试将 volatile 添加到变量定义中,然后再次 运行。然后编译器会认为变量可以从另一个线程修改并在每个循环中执行测试。