我有一个带内循环的简单函数 - 它缩放输入值,在查找表中查找输出值,并将其复制到目标.(ftol_ambient是我从网上复制的一种技巧,用于将float快速转换为int).
for (i = 0; i < iCount; ++i) { iScaled = ftol_ambient(*pSource * PRECISION3); if (iScaled <= 0) *pDestination = 0; else if (iScaled >= PRECISION3) *pDestination = 255; else { iSRGB = FloatToSRGBTable3[iScaled]; *pDestination = iSRGB; } pSource++; pDestination++; }
现在我的查找表是有限的,并且浮点数是无限的,因此有可能出现一个一个错误.我用一些代码创建了一个函数副本来处理这种情况.请注意,唯一的区别是添加了2行代码 - 请忽略丑陋的指针转换.
for (i = 0; i < iCount; ++i) { iScaled = ftol_ambient(*pSource * PRECISION3); if (iScaled <= 0) *pDestination = 0; else if (iScaled >= PRECISION3) *pDestination = 255; else { iSRGB = FloatToSRGBTable3[iScaled]; if (((int *)SRGBCeiling)[iSRGB] <= *((int *)pSource)) ++iSRGB; *pDestination = (unsigned char) iSRGB; } pSource++; pDestination++; }
这是奇怪的部分.我正在测试两个版本,输入相同的100000个元素,重复100次.在我的Athlon 64 1.8 GHz(32位模式)上,第一个功能需要0.231秒,第二个(更长)功能需要0.185秒.两个函数在相同的源文件中相邻,因此不可能有不同的编译器设置.我已经多次运行测试,扭转它们运行的顺序,每次的时间大致相同.
我知道现代处理器有很多神秘之处,但这怎么可能呢?
这里用于比较Microsoft VC++ 6编译器的相关汇编器输出.
; 173 : for (i = 0; i < iCount; ++i) $L4455: ; 174 : { ; 175 : iScaled = ftol_ambient(*pSource * PRECISION3); fld DWORD PTR [esi] fmul DWORD PTR __real@4@400b8000000000000000 fstp QWORD PTR $T5011[ebp] ; 170 : int i; ; 171 : int iScaled; ; 172 : unsigned int iSRGB; fld QWORD PTR $T5011[ebp] ; 173 : for (i = 0; i < iCount; ++i) fistp DWORD PTR _i$5009[ebp] ; 176 : if (iScaled <= 0) mov edx, DWORD PTR _i$5009[ebp] test edx, edx jg SHORT $L4458 ; 177 : *pDestination = 0; mov BYTE PTR [ecx], 0 ; 178 : else if (iScaled >= PRECISION3) jmp SHORT $L4461 $L4458: cmp edx, 4096 ; 00001000H jl SHORT $L4460 ; 179 : *pDestination = 255; mov BYTE PTR [ecx], 255 ; 000000ffH ; 180 : else jmp SHORT $L4461 $L4460: ; 181 : { ; 182 : iSRGB = FloatToSRGBTable3[iScaled]; ; 183 : *pDestination = (unsigned char) iSRGB; mov dl, BYTE PTR _FloatToSRGBTable3[edx] mov BYTE PTR [ecx], dl $L4461: ; 184 : } ; 185 : pSource++; add esi, 4 ; 186 : pDestination++; inc ecx dec edi jne SHORT $L4455
$L4472: ; 199 : { ; 200 : iScaled = ftol_ambient(*pSource * PRECISION3); fld DWORD PTR [esi] fmul DWORD PTR __real@4@400b8000000000000000 fstp QWORD PTR $T4865[ebp] ; 195 : int i; ; 196 : int iScaled; ; 197 : unsigned int iSRGB; fld QWORD PTR $T4865[ebp] ; 198 : for (i = 0; i < iCount; ++i) fistp DWORD PTR _i$4863[ebp] ; 201 : if (iScaled <= 0) mov edx, DWORD PTR _i$4863[ebp] test edx, edx jg SHORT $L4475 ; 202 : *pDestination = 0; mov BYTE PTR [edi], 0 ; 203 : else if (iScaled >= PRECISION3) jmp SHORT $L4478 $L4475: cmp edx, 4096 ; 00001000H jl SHORT $L4477 ; 204 : *pDestination = 255; mov BYTE PTR [edi], 255 ; 000000ffH ; 205 : else jmp SHORT $L4478 $L4477: ; 206 : { ; 207 : iSRGB = FloatToSRGBTable3[iScaled]; xor ecx, ecx mov cl, BYTE PTR _FloatToSRGBTable3[edx] ; 208 : if (((int *)SRGBCeiling)[iSRGB] <= *((int *)pSource)) mov edx, DWORD PTR _SRGBCeiling[ecx*4] cmp edx, DWORD PTR [esi] jg SHORT $L4481 ; 209 : ++iSRGB; inc ecx $L4481: ; 210 : *pDestination = (unsigned char) iSRGB; mov BYTE PTR [edi], cl $L4478: ; 211 : } ; 212 : pSource++; add esi, 4 ; 213 : pDestination++; inc edi dec eax jne SHORT $L4472
int one = 1; int two = 2; if (one == two) ++iSRGB;
第一个功能的运行时间现在下降到0.152秒.有趣.
; 175 : for (i = 0; i < iCount; ++i) $L4457: ; 176 : { ; 177 : iScaled = ftol_ambient(*pSource * PRECISION3); fld DWORD PTR [edi] fmul DWORD PTR __real@4@400b8000000000000000 fstp QWORD PTR $T5014[ebp] ; 170 : int i; ; 171 : int iScaled; ; 172 : int one = 1; fld QWORD PTR $T5014[ebp] ; 173 : int two = 2; fistp DWORD PTR _i$5012[ebp] ; 178 : if (iScaled <= 0) mov esi, DWORD PTR _i$5012[ebp] test esi, esi jg SHORT $L4460 ; 179 : *pDestination = 0; mov BYTE PTR [edx], 0 ; 180 : else if (iScaled >= PRECISION3) jmp SHORT $L4463 $L4460: cmp esi, 4096 ; 00001000H jl SHORT $L4462 ; 181 : *pDestination = 255; mov BYTE PTR [edx], 255 ; 000000ffH ; 182 : else jmp SHORT $L4463 $L4462: ; 183 : { ; 184 : iSRGB = FloatToSRGBTable3[iScaled]; xor ecx, ecx mov cl, BYTE PTR _FloatToSRGBTable3[esi] ; 185 : if (one == two) ; 186 : ++iSRGB; ; 187 : *pDestination = (unsigned char) iSRGB; mov BYTE PTR [edx], cl $L4463: ; 188 : } ; 189 : pSource++; add edi, 4 ; 190 : pDestination++; inc edx dec eax jne SHORT $L4457
Nils Pipenbr.. 11
我的猜测是,在第一种情况下,两个不同的分支最终在CPU的同一分支预测槽中.如果这两个分支在每次代码减速时预测不同.
在第二循环中,添加的代码可能足以将一个分支移动到不同的分支预测时隙.
确保您可以尝试使用英特尔VTune分析器或AMD CodeAnalyst工具.这些工具将向您显示代码中的确切内容.
但是,请记住,进一步优化此代码很可能不值得.如果您在CPU上调整代码速度更快,则可能会在不同品牌上变慢.
编辑:
如果您想阅读分支预测,请尝试Agner Fog的优秀网站:http://www.agner.org/optimize/
该pdf详细解释了分支预测时隙分配:http://www.agner.org/optimize/microarchitecture.pdf
我的猜测是,在第一种情况下,两个不同的分支最终在CPU的同一分支预测槽中.如果这两个分支在每次代码减速时预测不同.
在第二循环中,添加的代码可能足以将一个分支移动到不同的分支预测时隙.
确保您可以尝试使用英特尔VTune分析器或AMD CodeAnalyst工具.这些工具将向您显示代码中的确切内容.
但是,请记住,进一步优化此代码很可能不值得.如果您在CPU上调整代码速度更快,则可能会在不同品牌上变慢.
编辑:
如果您想阅读分支预测,请尝试Agner Fog的优秀网站:http://www.agner.org/optimize/
该pdf详细解释了分支预测时隙分配:http://www.agner.org/optimize/microarchitecture.pdf