我写了一段C代码,以展示关于优化和分支预测的讨论中的一点。 然后我注意到比我预期的更多样化的结果。 我的目标是用C ++和C之间通用子集的语言编写它,这对于两种语言都是标准兼容的并且相当便携。 它在不同的Windows PC上进行了测试:
#include #include /// @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 %dn" , ms_elapsed(start, stop), sum ); start = clock(); sum = unpredictableIfs(); stop = clock(); printf( "Unpredictable ifs took %d msec; answer was %dn" , ms_elapsed(start, stop), sum ); start = clock(); sum = noIfs(); stop = clock(); printf( "Same without ifs took %d msec; answer was %dn" , ms_elapsed(start, stop), sum ); start = clock(); sum = unpredictableIfs(); stop = clock(); printf( "Unpredictable ifs took %d msec; answer was %dn" , 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”ReleaseTrying.pch“/ Fa”Release “/ Fo”Release “/ Fd”Releasevc100.pdb”/ Gd / analyze- / errorReport:队列
其他人发布了这样的…用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
现在我有疑问。 这里发生了什么?
更具体地说……固定function如何能够花费如此不同的时间? 我的代码中有什么问题吗? 英特尔处理器有什么棘手的问题吗? 编译器是做什么奇怪的吗? 是不是因为在64位处理器上运行了32位代码?
谢谢你的关注!
编辑:我接受g ++ -O1只是在其他2个调用中重用返回值。 我也接受g ++ -O2和g ++ -O3有缺陷导致优化。 测量速度的显着多样性(450%!!!)似乎仍然是神秘的。
我查看了VS2010生成的代码的反汇编。 它确实内联unpredictableIfs
3次。 内联代码非常相似; 循环是一样的。 它没有内联noIfs
。 它确实推出了noIfs
。 一次迭代需要4个步骤。 noIfs
计算就像写了一样,而unpredictableIfs
noIfs
使用jne
来跳过增量。
使用-O1
,gcc-4.7.1只调用一次unpredictableIfs
Ifs并重新使用结果,因为它识别它是一个纯函数,因此每次调用时结果都是相同的。 (我做了,通过查看生成的组件进行validation。)
具有更高的优化级别,函数被内联,并且编译器不再识别它是相同的代码,因此每次函数调用出现在源中时它都会运行。
除此之外,当使用-O1
或-O2
时,我的gcc-4.7.1最好处理unpredictableIfs
-O2
(除了重用问题,两者都产生相同的代码),而使用-O3
处理noIfs
要好得多。 然而,相同代码的不同运行之间的时间在这里是相同的 – 相差或相差10毫秒( clock
粒度),所以我不知道什么可能导致您为-O3
报告的unpredictableIfs
的-O3
的实质上不同的时间。
对于-O2
, unpredictableIfs
-O2
的循环与使用-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
的Ifs更快一点。
使用-O3
, unpredictableIfs
-O3
的循环变得更糟,
.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
运行速度几乎是其四倍。
是的,看看64位Linux上gcc的汇编代码,第一种情况,使用-O1,函数UnpredictableIfs
确实只调用一次,结果重用。
使用-O2和-O3时,函数内联,并且所需的时间应相同。 在任何一位代码中都没有实际的分支,但是两位代码的转换有些不同,我已经删除了更新“sum”的行[在两种情况下都在%edx
]
UnpredictableIfs:
movl %eax, %ecx andl $1073741826, %ecx cmpl $1, %ecx adcl $0, %edx addl $1, %eax
NoIfs:
xorl %ecx, %ecx testl $1073741826, %eax sete %cl addl $1, %eax addl %ecx, %edx
正如你所看到的,它并不完全相同,但它做的非常相似。
关于Windows上的结果范围(从1016毫秒到4797毫秒):您应该知道MSVC中的clock()
返回经过的挂起时间 。 该标准表示clock()
应返回该进程所花费的CPU时间的近似值,而其他实现则可以更好地完成此任务。
鉴于MSVC正在提供挂起时间,如果您的进程在运行一次迭代测试时被抢占,则可能会产生更大的结果,即使代码运行的CPU时间大致相同。
另请注意,许多Windows PC上的clock()
具有非常糟糕的分辨率,通常为11-19 ms。 你已经做了足够的迭代,只有1%左右,所以我不认为这是差异的一部分,但是在尝试编写基准时要注意这一点很好。 我知道你的可移植性,但如果你需要在Windows上更好的测量,你可以使用QueryPerformanceCounter
几乎肯定会给你更好的分辨率,尽管它仍然只是过去的时间。
更新:在我了解到一个案例的长运行时间一直在发生之后,我启动了VS2010并重现了结果。 对于某些运行,我通常得到大约1000毫秒的东西,对于其他运行,我通常得到750毫秒,对于莫名其妙的运行,我得到5000毫秒。
观察:
我不确定从这一切得出什么结论,除了volatile
有MSVC的不可预测的性能后果,所以你应该只在必要时应用它。
更新2:我看到一致的运行时差异与volatile的使用有关,即使反汇编几乎相同。
随着波动:
Puzzling measurements: Unpredictable ifs took 643 msec; answer was 500000000 Unpredictable ifs took 1248 msec; answer was 500000000 Unpredictable ifs took 605 msec; answer was 500000000 Unpredictable ifs took 4611 msec; answer was 500000000 Unpredictable ifs took 4706 msec; answer was 500000000 Unpredictable ifs took 4516 msec; answer was 500000000 Unpredictable ifs took 4382 msec; answer was 500000000
每个实例的反汇编如下所示:
start = clock(); 010D1015 mov esi,dword ptr [__imp__clock (10D20A0h)] 010D101B add esp,4 010D101E call esi 010D1020 mov dword ptr [start],eax sum = unpredictableIfs(); 010D1023 xor ecx,ecx 010D1025 xor eax,eax 010D1027 test eax,40000002h 010D102C jne main+2Fh (10D102Fh) 010D102E inc ecx 010D102F inc eax 010D1030 cmp eax,3B9ACA00h 010D1035 jl main+27h (10D1027h) 010D1037 mov dword ptr [sum],ecx stop = clock(); 010D103A call esi 010D103C mov dword ptr [stop],eax
没有不稳定:
Puzzling measurements: Unpredictable ifs took 644 msec; answer was 500000000 Unpredictable ifs took 624 msec; answer was 500000000 Unpredictable ifs took 624 msec; answer was 500000000 Unpredictable ifs took 605 msec; answer was 500000000 Unpredictable ifs took 599 msec; answer was 500000000 Unpredictable ifs took 599 msec; answer was 500000000 Unpredictable ifs took 599 msec; answer was 500000000 start = clock(); 00321014 mov esi,dword ptr [__imp__clock (3220A0h)] 0032101A add esp,4 0032101D call esi 0032101F mov dword ptr [start],eax sum = unpredictableIfs(); 00321022 xor ebx,ebx 00321024 xor eax,eax 00321026 test eax,40000002h 0032102B jne main+2Eh (32102Eh) 0032102D inc ebx 0032102E inc eax 0032102F cmp eax,3B9ACA00h 00321034 jl main+26h (321026h) stop = clock(); 00321036 call esi // The only optimization I see is here, where eax isn't explicitly stored // in stop but is instead immediately used to compute the value for the // printf that follows.
除了寄存器选择,我没有看到显着的差异。
需要了解更多c/c++开发分享难以衡量C / C ++性能,也可以关注C/ C++技术分享栏目—计算机技术网(www.ctvol.com)!
以上就是c/c++开发分享难以衡量C / C ++性能相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/979080.html