Joh*_*rer 31 c++ performance assembly inlining compiler-optimization
以C++编写的一个数值软件的以下关键循环基本上将两个对象的成员比较:
for(int j=n;--j>0;)
asd[j%16]=a.e<b.e;
Run Code Online (Sandbox Code Playgroud)
a
和b
是类ASD
:
struct ASD {
float e;
...
};
Run Code Online (Sandbox Code Playgroud)
我正在调查将此比较放在轻量级成员函数中的效果:
bool test(const ASD& y)const {
return e<y.e;
}
Run Code Online (Sandbox Code Playgroud)
并像这样使用它:
for(int j=n;--j>0;)
asd[j%16]=a.test(b);
Run Code Online (Sandbox Code Playgroud)
编译器正在内联这个函数,但问题是,汇编代码会有所不同,导致运行时开销超过10%.我要问:
为什么编译器会推出不同的汇编代码?
为什么生产的组件更慢?
编辑: 第二个问题已通过实施@ KamyarSouri的建议(j%16)得到了回答.汇编代码现在看起来几乎相同(请参阅http://pastebin.com/diff.php?i=yqXedtPm).唯一的区别是第18,33,48行:
000646F9 movzx edx,dl
Run Code Online (Sandbox Code Playgroud)
此图表显示了我的代码的50个测试的FLOP/s(最多为缩放因子).
用于生成绘图的gnuplot脚本:http://pastebin.com/8amNqya7
编译器选项:
/ Zi/W3/WX-/MP/Ox/Ob2/Oi/Ot/Oy/GL/D"WIN32"/ D"NDEBUG"/ D"_CONSOLE"/ D"_UNICODE"/ D"UNICODE"/ Gm-/EHsc/MT/GS-/Gy/arch:SSE2/fp:exact/Zc:wchar_t/Zc:forScope/Gd/analyze-
链接器选项:/ INCREMENTAL:NO"kernel32.lib""user32.lib""gdi32.lib""winspool.lib""comdlg32.lib""advapi32.lib""shell32.lib""ole32.lib""oleaut32. lib""uuid.lib""odbc32.lib""odbccp32.lib"/ ALLOWISOLATION/MANIFESTUAC:"level ='asInvoker'uiAccess ='false'"/ SUBSYSTEM:CONSOLE/OPT:REF/OPT:ICF/LTCG/TLBID :1/DYNAMICBASE/NXCOMPAT/MACHINE:X86/ERRORREPORT:QUEUE
Mys*_*ial 31
您的asd
数组声明为:
int *asd=new int[16];
Run Code Online (Sandbox Code Playgroud)
因此,使用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
Run Code Online (Sandbox Code Playgroud)
除第一条指令外,编译器内联版本完全相同.
而不是:
xor eax,eax
Run Code Online (Sandbox Code Playgroud)
它有:
xor eax,eax
movzx edx,al
Run Code Online (Sandbox Code Playgroud)
好的,这是一条额外的指令.他们都做同样的事情 - 归零寄存器.这是我看到的唯一区别......
该movzx
指令0.33
在所有较新的架构上具有单周期延迟和周期倒数吞吐量.所以我无法想象这会如何产生10%的差异.
在这两种情况下,归零的结果仅在以后使用3条指令.因此,很可能这可能是关键的执行路径.
虽然我不是英特尔工程师,但我的猜测是:
大多数现代处理器xor eax,eax
通过寄存器重命名到零寄存器组来处理归零操作(例如).它完全绕过执行单元.但是,当通过访问(部分)寄存器时,这种特殊处理可能会导致管道冒泡movzx edi,al
.
此外,在编译器内联版本中也存在错误依赖eax
:
movzx edx,al
mov eax,ecx // False dependency on "eax".
Run Code Online (Sandbox Code Playgroud)
无论是否乱序执行能够解决,这是超越我.
在这里,我将解释为什么movzx
产生额外的原因以及为什么它会停留.
这里的关键是bool
返回值.显然,bool
数据类型可能是MSVC内部表示中存储的8位值.因此,当您隐式转换bool
为int
此处时:
asd[j%16] = a.test(b);
^^^^^^^^^ ^^^^^^^^^
type int type bool
Run Code Online (Sandbox Code Playgroud)
有一个8位 - > 32位整数提升.这就是MSVC生成movzx
指令的原因.
当手动完成内联时,编译器有足够的信息来优化这种转换,并将所有内容保存为32位数据类型IR.
但是,当代码使用bool
返回值放入其自己的函数时,编译器无法优化8位中间数据类型.因此,movzx
留下来.
当你使双方数据类型相同(或int
或bool
),无需转换.因此完全避免了这个问题.
归档时间: |
|
查看次数: |
1091 次 |
最近记录: |