以C ++编写的数字软件的以下关键循环基本上将其中一个成员的两个对象进行比较:
for(int j=n;--j>0;)
asd[j%16]=a.e<b.e;
a
和b
属于ASD
类:
struct ASD {
float e;
...
};
我正在调查将此比较放在轻量级成员函数中的效果:
bool test(const ASD& y)const {
return e<y.e;
}
并像这样使用它:
for(int j=n;--j>0;)
asd[j%16]=a.test(b);
编译器正在内联此函数,但问题是,汇编代码将不同并导致> 10%的运行时开销。我不得不质疑:
为什么编译器会推出不同的汇编代码?
为什么生产的程序集会变慢?
编辑:第二个问题已经通过实施@ KamyarSouri的建议(j%16)得到了回答。汇编代码现在看起来几乎相同(参见http://pastebin.com/diff.php?i=yqXedtPm)。唯一的区别是第18,33,48行:
000646F9 movzx edx,dl
此图表显示了我的代码的50个测试的FLOP / s(最多为缩放系数)。
用于生成绘图的gnuplot脚本:http://pastebin.com/8amNqya7
编译器选项:
/ Zi / W3 / WX- / MP / Ox / Ob2 / Oi / Ot / Oy / GL / D&#34; WIN32&#34; / D&#34; NDEBUG&#34; / D&#34; _CONSOLE&#34; / D&#34; _UNICODE&#34; / D&#34; UNICODE&#34; / Gm- / EHsc / MT / GS- / Gy / arch:SSE2 / fp:precise / Zc:wchar_t / Zc:forScope / Gd / analyze -
链接器选项: / INCREMENTAL:NO&#34; kernel32.lib&#34; &#34; USER32.LIB&#34; &#34; GDI32.LIB&#34; &#34; winspool.lib&#34; &#34; comdlg32.lib&#34; &#34; advapi32.lib&#34; &#34; SHELL32.LIB&#34; &#34; ole32.lib&#34; &#34; oleaut32.lib&#34, &#34; UUID.LIB&#34; &#34; odbc32.lib&#34; &#34; odbccp32.lib&#34; / ALLOWISOLATION / MANIFESTUAC:&#34; level =&#39; asInvoker&#39; uiAccess =&#39;假&#39;&#34; / SUBSYSTEM:CONSOLE / OPT:REF / OPT:ICF / LTCG / TLBID:1 / DYNAMICBASE / NXCOMPAT / MACHINE:X86 / ERRORREPORT:QUEUE
答案 0 :(得分:31)
您的asd
数组声明为:
int *asd=new int[16];
因此,请使用int
作为返回类型而不是bool.
或者,将数组类型更改为bool
。
在任何情况下,使test
函数的返回类型与数组的类型匹配。
跳到底部了解更多详情。
在手动内联版本中,一次迭代的“核心”如下所示:
xor eax,eax
mov edx,ecx
and edx,0Fh
mov dword ptr [ebp+edx*4],eax
mov eax,dword ptr [esp+1Ch]
movss xmm0,dword ptr [eax]
movss xmm1,dword ptr [edi]
cvtps2pd xmm0,xmm0
cvtps2pd xmm1,xmm1
comisd xmm1,xmm0
除第一条指令外,编译器内联版本完全相同。
而不是:
xor eax,eax
它有:
xor eax,eax
movzx edx,al
好的,所以它是一个额外指令。他们都做同样的事情 - 归零寄存器。这是我看到的唯一区别......
movzx
指令在所有较新的体系结构上具有单周期延迟和0.33
周期倒数吞吐量。所以我无法想象这会如何产生10%的差异。
在这两种情况下,归零的结果仅在以后使用3条指令。所以很有可能这可能是关键的执行路径。
虽然我不是英特尔工程师,但这是我的猜测:
大多数现代处理器通过register renaming处理归零操作(例如xor eax,eax
)到一组零寄存器。它完全绕过执行单元。但是,当通过movzx edi,al
访问(部分)寄存器时,这种特殊处理可能会导致管道冒泡。
此外,编译器内联版本中eax
还存在 false 依赖关系:
movzx edx,al
mov eax,ecx // False dependency on "eax".
out-of-order execution能否解决这个问题超出了我的范围。
在这里,我将解释 为什么 生成额外的movzx
以及它保留的原因。
此处的关键是bool
返回值。显然,bool
数据类型可能是MSVC内部表示中存储的8位值。
因此,当您在此隐式转换bool
到int
时:
asd[j%16] = a.test(b);
^^^^^^^^^ ^^^^^^^^^
type int type bool
有一个8位 - &gt; 32位整数提升。这就是MSVC生成movzx
指令的原因。
当手动完成内联时,编译器有足够的信息来优化此转换,并将所有内容保存为32位数据类型IR。
但是,当代码被置于具有bool
返回值的自己的函数中时,编译器无法优化8位中间数据类型。因此,movzx
会停留。
如果两种数据类型相同(int
或bool
),则不需要转换。因此,完全避免了这个问题。
答案 1 :(得分:1)
lea esp,[esp]
占用7个字节的i-cache并且它在循环内部。其他一些线索使得看起来编译器不确定这是发布版本还是调试版本。
编辑:
lea esp,[esp]
不在循环中。周围指令中的位置误导了我。现在它看起来有意浪费7个字节,然后是另外浪费的2个字节,以便在16字节边界处启动实际循环。这意味着这实际上加快了速度,正如Johennes Gerer所观察到的那样。
编译器似乎仍不确定这是否是调试或发布版本。
另一个编辑:
pastebin diff与我之前看到的pastebin diff不同。这个答案现在可以删除,但它已经有了评论,所以我会留下它。