根据我之前的question我的想法是通过删除系数 m_a、m_b 为 1.0 或 0.0 时的计算来优化算法。现在我尝试优化算法并得到了一些我无法解释的奇怪结果。
第一个分析器运行 100k 个样本。从文件中读取参数值(!):
b0=1.0 b1=-1.480838022915731 b2=1.0
a0=1.0 a1=-1.784147570544337 a2=0.854309980957510
第二个分析器运行相同的 100k 个样本。从文件中读取参数值(!):
b0=1.0 b1=-1.480838022915731 b2=1.0
a0=1.0 a1=-1.784147570544337 a2=0.0 <--- 只有 a2 不同!
图中左侧的数字(灰色背景)代表所需的 CPU 周期。清晰可见,参数 a2=0.0 的第二次运行要快得多。
我检查了调试和发布代码之间的区别。发布代码更快(如预期)。当修改参数 a2 时,调试和发布代码具有相同的奇怪行为。
然后我检查了ASM代码。我注意到使用了 SSE 指令。这是有效的,因为我使用/arch:SSE2 编译。因此我禁用了 SSE。生成的代码不再使用 SSE,并且性能不再依赖于参数值 a2(正如预期的那样)
因此我得出结论,当使用 SSE 并且 SSE 引擎检测到 a2 为 0.0 时,它们是某种性能优势,因此省略了过时的乘法和减法。我从来没有听说过这个,并试图找到信息但没有成功。
那么有人可以解释我的表现结果吗?
为了完整起见,这是发布版本的相关 ASM 代码:
00F43EC0 mov edx,dword ptr [ebx]
00F43EC2 movss xmm0,dword ptr [eax+edi*4]
00F43EC7 cmp edx,dword ptr [ebx+4]
00F43ECA je $LN419+193h (0F43F9Dh)
00F43ED0 mov esi,dword ptr [ebx+4]
00F43ED3 lea eax,[edx+68h]
00F43ED6 lea ecx,[eax-68h]
00F43ED9 cvtps2pd xmm0,xmm0
00F43EDC cmp ecx,esi
00F43EDE je $LN419+180h (0F43F8Ah)
00F43EE4 movss xmm1,dword ptr [eax+4]
00F43EE9 mov ecx,dword ptr [eax]
00F43EEB mov edx,dword ptr [eax-24h]
00F43EEE movss xmm3,dword ptr [edx+4]
00F43EF3 cvtps2pd xmm1,xmm1
00F43EF6 mulsd xmm1,xmm0
00F43EFA movss xmm0,dword ptr [ecx]
00F43EFE cvtps2pd xmm4,xmm0
00F43F01 cvtps2pd xmm3,xmm3
00F43F04 mulsd xmm3,xmm4
00F43F08 xorps xmm2,xmm2
00F43F0B cvtpd2ps xmm2,xmm1
00F43F0F movss xmm1,dword ptr [ecx+4]
00F43F14 cvtps2pd xmm4,xmm1
00F43F17 cvtps2pd xmm2,xmm2
00F43F1A subsd xmm2,xmm3
00F43F1E movss xmm3,dword ptr [edx+8]
00F43F23 mov edx,dword ptr [eax-48h]
00F43F26 cvtps2pd xmm3,xmm3
00F43F29 mulsd xmm3,xmm4
00F43F2D subsd xmm2,xmm3
00F43F31 movss xmm3,dword ptr [edx+4]
00F43F36 cvtps2pd xmm4,xmm0
00F43F39 cvtps2pd xmm3,xmm3
00F43F3C mulsd xmm3,xmm4
00F43F40 movss xmm4,dword ptr [edx]
00F43F44 cvtps2pd xmm4,xmm4
00F43F47 cvtpd2ps xmm2,xmm2
00F43F4B xorps xmm5,xmm5
00F43F4E cvtss2sd xmm5,xmm2
00F43F52 mulsd xmm4,xmm5
00F43F56 addsd xmm3,xmm4
00F43F5A movss xmm4,dword ptr [edx+8]
00F43F5F cvtps2pd xmm1,xmm1
00F43F62 movss dword ptr [ecx+4],xmm0
00F43F67 mov edx,dword ptr [eax]
00F43F69 cvtps2pd xmm4,xmm4
00F43F6C mulsd xmm4,xmm1
00F43F70 addsd xmm3,xmm4
00F43F74 xorps xmm1,xmm1
00F43F77 cvtpd2ps xmm1,xmm3
00F43F7B movss dword ptr [edx],xmm2
00F43F7F movaps xmm0,xmm1
00F43F82 add eax,70h
00F43F85 jmp $LN419+0CCh (0F43ED6h)
00F43F8A movss xmm1,dword ptr [ebx+10h]
00F43F8F cvtps2pd xmm1,xmm1
00F43F92 mulsd xmm1,xmm0
00F43F96 xorps xmm0,xmm0
00F43F99 cvtpd2ps xmm0,xmm1
00F43F9D mov eax,dword ptr [ebp-4Ch]
00F43FA0 movss dword ptr [eax+edi*4],xmm0
00F43FA5 mov ecx,dword ptr [ebp-38h]
00F43FA8 mov eax,dword ptr [ebp-3Ch]
00F43FAB sub ecx,eax
00F43FAD inc edi
00F43FAE sar ecx,2
00F43FB1 cmp edi,ecx
00F43FB3 jb $LN419+0B6h (0F43EC0h)
编辑:用发布代码替换调试 ASM 代码。
最佳答案
SSE 上的 FP 乘法没有早期出局。这是一个具有短延迟的完全流水线操作,因此添加早期输出会使指令退出复杂化,同时提供零性能优势。在现代处理器上通常具有数据相关执行特征的唯一指令是除法和平方根(忽略次正规,它会影响更广泛的指令)。英特尔和 AMD 以及 Agner Fog 都对此进行了广泛记录。
那么为什么您会看到性能发生变化?最可能的解释是,由于输入或结果不正常,您遇到了停顿;这在 DSP 滤波器和延迟中很常见,就像您拥有的那样。在没有看到您的代码和输入数据的情况下,无法确定这就是正在发生的事情,但这是迄今为止最有可能的解释。如果是这样,您可以通过在 MXCSR 中设置 DAZ 和 FTZ 位来解决问题。
英特尔文档:
http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf (请参阅附录中的延迟表,注意 mulss
和 mulsd
有一个固定值。)
AMD 16 小时指令延迟(excel 电子表格): http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/AMD64_16h_InstrLatency_1.1.xlsx
Agner Fog 的英特尔和 AMD 指令延迟表: http://www.agner.org/optimize/instruction_tables.pdf
关于c++ - SSE FP 单元是否检测到 0.0 个操作数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26816523/