纯 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 倍。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 C++ 包装类没有被内联?

Java 使用数组比 C++ 中的 std::vector 快 8 倍。我做错了啥?

为什么(我的)Java比C ++快25倍?

比Hadoop快10倍!50岁高龄程序员刚开源了10万行大数据平台代码

横空出世!IDEA画图神器来了,比Visio快10倍!

Firefox引入新的JS解释器,编译速度比Chrome快10倍!