我编写了一段 C 代码来说明关于优化和分支预测的讨论中的一个观点。然后我注意到比我预期的更多样化的结果。我的目标是用一种介于 C++ 和 C 之间的通用子集的语言编写它,这两种语言都符合标准并且相当可移植。它在不同的 Windows PC 上进行了测试:
#include <stdio.h>
#include <time.h>
/// @return - time difference between start and stop in milliseconds
int ms_elapsed( clock_t start, clock_t stop )
{
return (int)( 1000.0 * ( stop - start ) / CLOCKS_PER_SEC );
}
int const Billion = 1000000000;
/// & with numbers up to Billion gives 0, 0, 2, 2 repeating pattern
int const Pattern_0_0_2_2 = 0x40000002;
/// @return - half of Billion
int unpredictableIfs()
{
int sum = 0;
for ( int i = 0; i < Billion; ++i )
{
// true, true, false, false ...
if ( ( i & Pattern_0_0_2_2 ) == 0 )
{
++sum;
}
}
return sum;
}
/// @return - half of Billion
int noIfs()
{
int sum = 0;
for ( int i = 0; i < Billion; ++i )
{
// 1, 1, 0, 0 ...
sum += ( i & Pattern_0_0_2_2 ) == 0;
}
return sum;
}
int main()
{
clock_t volatile start;
clock_t volatile stop;
int volatile sum;
printf( "Puzzling measurements:\n" );
start = clock();
sum = unpredictableIfs();
stop = clock();
printf( "Unpredictable ifs took %d msec; answer was %d\n"
, ms_elapsed(start, stop), sum );
start = clock();
sum = unpredictableIfs();
stop = clock();
printf( "Unpredictable ifs took %d msec; answer was %d\n"
, ms_elapsed(start, stop), sum );
start = clock();
sum = noIfs();
stop = clock();
printf( "Same without ifs took %d msec; answer was %d\n"
, ms_elapsed(start, stop), sum );
start = clock();
sum = unpredictableIfs();
stop = clock();
printf( "Unpredictable ifs took %d msec; answer was %d\n"
, ms_elapsed(start, stop), sum );
}
用VS2010编译;/O2 优化 Intel Core 2、WinXP 结果:
Puzzling measurements:
Unpredictable ifs took 1344 msec; answer was 500000000
Unpredictable ifs took 1016 msec; answer was 500000000
Same without ifs took 1031 msec; answer was 500000000
Unpredictable ifs took 4797 msec; answer was 500000000
编辑:编译器的完整切换:
/Zi /nologo /W3 /WX- /O2 /Oi /Oy- /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm- /EHsc /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Fp"Release\Trying.pch" /Fa"Release\" /Fo"Release\" /Fd"Release\vc100.pdb" /Gd /analyze- /errorReport:queue
其他人发了这样的……用MinGW编译,g++ 4.71,-O1优化Intel Core 2,WinXP结果:
Puzzling measurements:
Unpredictable ifs took 1656 msec; answer was 500000000
Unpredictable ifs took 0 msec; answer was 500000000
Same without ifs took 1969 msec; answer was 500000000
Unpredictable ifs took 0 msec; answer was 500000000
他还发布了这样的 -O3 优化结果:
Puzzling measurements:
Unpredictable ifs took 1890 msec; answer was 500000000
Unpredictable ifs took 2516 msec; answer was 500000000
Same without ifs took 1422 msec; answer was 500000000
Unpredictable ifs took 2516 msec; answer was 500000000
现在我有问题。这是怎么回事?
更具体地说......一个固定的功能怎么会花费如此不同的时间?我的代码有问题吗?英特尔处理器有什么棘手的问题吗?编译器是否在做一些奇怪的事情?可能是因为 32 位代码在 64 位处理器上运行?
感谢关注!
编辑: 我接受 g++ -O1 只是在其他 2 个调用中重用返回值。我也接受 g++ -O2 和 g++ -O3 存在缺陷,导致优化被排除在外。测量速度的显着差异(450% !!!)似乎仍然很神秘。
我查看了 VS2010 生成的代码的反汇编。它内联 unpredictableIfs
3 次。内联代码非常相似。循环是一样的。它没有内联 noIfs
。它确实推出了一点 noIfs
。一次迭代需要 4 个步骤。 noIfs
像写的一样计算,而 unpredictableIfs
使用 jne
跳过增量。
最佳答案
使用 -O1
,gcc-4.7.1 只调用一次 unpredictableIfs
并重用结果,因为它认识到它是一个纯函数,所以结果将是每次调用都一样。 (我的确实如此,通过查看生成的程序集进行验证。)
使用更高的优化级别,函数是内联的,编译器不再识别它是相同的代码,因此每次在源代码中出现函数调用时都会运行它。
除此之外,我的 gcc-4.7.1 在使用 -O1
或 -O2
时最好处理 unpredictableIfs
(除了重用问题,两者都产生相同的代码),而 noIfs
被 -O3
处理 much 更好。然而,相同代码的不同运行之间的时间在这里是一致的 - 等于或不同 10 毫秒(clock
的粒度),所以我不知道是什么导致 的时间大不相同您为
。-O3
报告的不可预测的Ifs
使用 -O2
,unpredictableIfs
的循环与使用 -O1
生成的代码相同(寄存器交换除外):
.L12:
movl %eax, %ecx
andl $1073741826, %ecx
cmpl $1, %ecx
adcl $0, %edx
addl $1, %eax
cmpl $1000000000, %eax
jne .L12
对于 noIfs
也是类似的:
.L15:
xorl %ecx, %ecx
testl $1073741826, %eax
sete %cl
addl $1, %eax
addl %ecx, %edx
cmpl $1000000000, %eax
jne .L15
在哪里
.L7:
testl $1073741826, %edx
sete %cl
movzbl %cl, %ecx
addl %ecx, %eax
addl $1, %edx
cmpl $1000000000, %edx
jne .L7
使用 -O1
。两个循环的运行时间相似,unpredictableIfs
快一点。
使用 -O3
,unpredictableIfs
的循环变得更糟,
.L14:
leal 1(%rdx), %ecx
testl $1073741826, %eax
cmove %ecx, %edx
addl $1, %eax
cmpl $1000000000, %eax
jne .L14
对于 noIfs
(包括这里的设置代码),它变得更好:
pxor %xmm2, %xmm2
movq %rax, 32(%rsp)
movdqa .LC3(%rip), %xmm6
xorl %eax, %eax
movdqa .LC2(%rip), %xmm1
movdqa %xmm2, %xmm3
movdqa .LC4(%rip), %xmm5
movdqa .LC5(%rip), %xmm4
.p2align 4,,10
.p2align 3
.L18:
movdqa %xmm1, %xmm0
addl $1, %eax
paddd %xmm6, %xmm1
cmpl $250000000, %eax
pand %xmm5, %xmm0
pcmpeqd %xmm3, %xmm0
pand %xmm4, %xmm0
paddd %xmm0, %xmm2
jne .L18
.LC2:
.long 0
.long 1
.long 2
.long 3
.align 16
.LC3:
.long 4
.long 4
.long 4
.long 4
.align 16
.LC4:
.long 1073741826
.long 1073741826
.long 1073741826
.long 1073741826
.align 16
.LC5:
.long 1
.long 1
.long 1
.long 1
它一次计算四次迭代,因此 noIfs
的运行速度几乎是当时的四倍。
关于c++ - 难以衡量 C/C++ 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14965553/