纯 C++ 代码比内联汇编程序快 10 倍。为啥?
Posted
技术标签:
【中文标题】纯 C++ 代码比内联汇编程序快 10 倍。为啥?【英文标题】:Plain C++ Code 10 times faster than inline assembler. Why?纯 C++ 代码比内联汇编程序快 10 倍。为什么? 【发布时间】:2015-09-03 21:26:29 【问题描述】:这两个代码 sn-ps 做同样的事情:将两个浮点数组相加并将结果存储回其中。
内联汇编器:
void vecAdd_SSE(float* v1, float* v2)
_asm
mov esi, v1
mov edi, v2
movups xmm0, [esi]
movups xmm1, [edi]
addps xmm0, xmm1
movups [esi], xmm0
movups [edi], xmm0
纯 C++ 代码:
void vecAdd_Std(float* v1, float* v2)
v1[0] = v1[0]+ v2[0];
v1[1] = v1[1]+ v2[1];
v1[2] = v1[2]+ v2[2];
v1[3] = v1[3]+ v2[3];
v2[0] = v1[0];
v2[1] = v1[1];
v2[2] = v1[2];
v2[3] = v1[3];
C++ 代码的反汇编(在调试模式下进行的反汇编,因为由于某种原因我无法在发布模式下查看反汇编):
void vecAdd_Std(float* v1, float* v2)
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
v1[0] = v1[0]+ v2[0];
mov eax,4
imul ecx,eax,0
mov edx,4
imul eax,edx,0
mov edx,dword ptr [v1]
mov esi,dword ptr [v2]
movss xmm0,dword ptr [edx+ecx]
addss xmm0,dword ptr [esi+eax]
mov eax,4
imul ecx,eax,0
mov edx,dword ptr [v1]
movss dword ptr [edx+ecx],xmm0
v1[1] = v1[1]+ v2[1];
mov eax,4
shl eax,0
v1[1] = v1[1]+ v2[1];
mov ecx,4
shl ecx,0
mov edx,dword ptr [v1]
mov esi,dword ptr [v2]
movss xmm0,dword ptr [edx+eax]
addss xmm0,dword ptr [esi+ecx]
mov eax,4
shl eax,0
mov ecx,dword ptr [v1]
movss dword ptr [ecx+eax],xmm0
v1[2] = v1[2]+ v2[2];
mov eax,4
shl eax,1
mov ecx,4
shl ecx,1
mov edx,dword ptr [v1]
mov esi,dword ptr [v2]
movss xmm0,dword ptr [edx+eax]
addss xmm0,dword ptr [esi+ecx]
mov eax,4
shl eax,1
mov ecx,dword ptr [v1]
movss dword ptr [ecx+eax],xmm0
v1[3] = v1[3]+ v2[3];
mov eax,4
imul ecx,eax,3
mov edx,4
imul eax,edx,3
mov edx,dword ptr [v1]
mov esi,dword ptr [v2]
movss xmm0,dword ptr [edx+ecx]
addss xmm0,dword ptr [esi+eax]
mov eax,4
imul ecx,eax,3
mov edx,dword ptr [v1]
movss dword ptr [edx+ecx],xmm0
v2[0] = v1[0];
mov eax,4
imul ecx,eax,0
mov edx,4
imul eax,edx,0
mov edx,dword ptr [v2]
mov esi,dword ptr [v1]
mov ecx,dword ptr [esi+ecx]
mov dword ptr [edx+eax],ecx
v2[1] = v1[1];
mov eax,4
shl eax,0
mov ecx,4
shl ecx,0
mov edx,dword ptr [v2]
mov esi,dword ptr [v1]
mov eax,dword ptr [esi+eax]
mov dword ptr [edx+ecx],eax
v2[2] = v1[2];
mov eax,4
shl eax,1
mov ecx,4
shl ecx,1
mov edx,dword ptr [v2]
mov esi,dword ptr [v1]
mov eax,dword ptr [esi+eax]
mov dword ptr [edx+ecx],eax
v2[3] = v1[3];
mov eax,4
imul ecx,eax,3
mov edx,4
imul eax,edx,3
mov edx,dword ptr [v2]
mov esi,dword ptr [v1]
mov ecx,dword ptr [esi+ecx]
mov dword ptr [edx+eax],ecx
现在我对这些函数进行了时间测量,发现内联汇编代码需要大约 10 倍的时间(在发布模式下)。 有人知道为什么吗?
【问题讨论】:
能否展示C++代码的反汇编进行对比? 同时指定您使用的编译器。 (看起来像 VC++?) 你是如何测量的? 那个反汇编肯定不是发布模式! @BoPersson 没有区别:如果我使用 movaps 和对齐的浮点数组,性能没有区别 【参考方案1】:在我的机器上(VS2015 64位模式),编译器内联vecAdd_Std
并产生
00007FF625921C8F vmovups xmm1,xmmword ptr [__xmm@4100000040c000004080000040000000 (07FF625929D60h)]
00007FF625921C97 vmovups xmm4,xmm1
00007FF625921C9B vcvtss2sd xmm1,xmm1,xmm4
测试代码
int main()
float x[4] = 1.0, 2.0, 3.0, 4.0;
float y[4] = 1.0, 2.0, 3.0, 4.0;
vecAdd_Std(x, y);
std::cout << x[0];
【讨论】:
好的,这回答了我的问题。现在很清楚为什么我不能在这个函数中设置断点。非常感谢您的回答。 那是作弊,您使用了两次相同的浮点向量,从而消除了一次内存负载:) @Cross_ - 这并不是一个“公平的基准测试”,而是为了表明 Philinator 的反汇编与编译器在发布模式下产生的结果相差甚远。而且那个“手工优化”的程序集并不是你能自动获得的最好的代码。 @Philinator:您可以通过在单独的翻译单元中编译函数并链接到目标文件来设置断点。话又说回来,编译后的 C 和手写程序集之间的大部分性能差异也可能会消失。【参考方案2】:您并没有真正调用执行一个 SSE 指令的函数,是吗?设置 xmm 寄存器涉及不小的开销,并且您将值从内存复制到寄存器并返回,这将花费比实际计算更长的时间。
如果发现编译器内联了 C++ 版本的函数,我一点也不感到惊讶,但对于包含内联汇编的函数并没有(实际上不能)这样做。
【讨论】:
以上是关于纯 C++ 代码比内联汇编程序快 10 倍。为啥?的主要内容,如果未能解决你的问题,请参考以下文章
Java 使用数组比 C++ 中的 std::vector 快 8 倍。我做错了啥?