7

我正在尝试实现一些内联汇编程序(在 Visual Studio 2012 C++ 代码中)以利用 SSE。我想添加 7 个数字 1e9 次,所以我将它们从 RAM 放置到 CPU 的 xmm0 到 xmm6 寄存器。当我使用以下代码在 Visual Studio 2012 中使用内联汇编执行此操作时:

C++ 代码:

for(int i=0;i<count;i++)
        resVal+=val1+val2+val3+val4+val5+val6+val7;

我的 ASM 代码:

int count=1000000000;

    double resVal=0.0;
       //placing values to register
    __asm{  
        movsd xmm0,val1;placing var1 in xmm0 register  
        movsd xmm1,val2  
        movsd xmm2,val3  
        movsd xmm3,val4  
        movsd xmm4,val5  
        movsd xmm5,val6  
        movsd xmm6,val7  
        pxor xmm7,xmm7;//turns xmm7 to zero
         }

    for(int i=0;i<count;i++)
    {
        __asm
        {
            addsd xmm7,xmm0;//+=var1
            addsd xmm7,xmm1;//+=var2
            addsd xmm7,xmm2;
            addsd xmm7,xmm3;
            addsd xmm7,xmm4;
            addsd xmm7,xmm5;
            addsd xmm7,xmm6;//+=var7
        }

    }

    __asm
        {
            movsd resVal,xmm7;//placing xmm7 into resVal
        }

这是来自 C++ 编译器的反汇编代码,用于代码“resVal+=val1+val2+val3+val4+val5+val6+val7”:

movsd       xmm0,mmword ptr [val1]  
addsd       xmm0,mmword ptr [val2]  
addsd       xmm0,mmword ptr [val3]  
addsd       xmm0,mmword ptr [val4]  
addsd       xmm0,mmword ptr [val5]  
addsd       xmm0,mmword ptr [val6]  
addsd       xmm0,mmword ptr [val7]  
addsd       xmm0,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm0  

可以看出,编译器只使用一个 xmm0 寄存器,其他时候它从 RAM 中获取值。

两种代码(我的 ASM 代码和 c++ 代码)的答案是相同的,但是 c++ 代码大约需要我的 asm 代码一半的时间来执行!

我读过关于 CPU 寄存器的信息,使用它们比内存快得多。我不认为这个比例是真的。为什么 asm 版本的 C++ 代码性能较低?

4

2 回答 2

11
  • 一旦数据在缓存中(在第一个循环之后就是这种情况,如果它还没有的话),使用内存或寄存器几乎没有什么区别。
  • 首先,浮点加法将比单个周期花费更长的时间。
  • 最终存储resVal“解开”xmm0 寄存器以允许寄存器自由“重命名”,这允许更多循环并行运行。

这是“除非你绝对确定,否则将代码留给编译器”的典型案例。

上面的最后一个项目解释了为什么代码比循环的每一步都依赖于先前计算的结果的代码更快。

在编译器生成的代码中,循环可以执行以下等效操作:

movsd       xmm0,mmword ptr [val1]  
addsd       xmm0,mmword ptr [val2]  
addsd       xmm0,mmword ptr [val3]  
addsd       xmm0,mmword ptr [val4]  
addsd       xmm0,mmword ptr [val5]  
addsd       xmm0,mmword ptr [val6]  
addsd       xmm0,mmword ptr [val7]  
addsd       xmm0,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm0  

movsd       xmm1,mmword ptr [val1]  
addsd       xmm1,mmword ptr [val2]  
addsd       xmm1,mmword ptr [val3]  
addsd       xmm1,mmword ptr [val4]  
addsd       xmm1,mmword ptr [val5]  
addsd       xmm1,mmword ptr [val6]  
addsd       xmm1,mmword ptr [val7]  
addsd       xmm1,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm1

现在,如您所见,我们可以“混合”这两个“线程”:

movsd       xmm0,mmword ptr [val1]  
movsd       xmm1,mmword ptr [val1]  
addsd       xmm0,mmword ptr [val2]  
addsd       xmm1,mmword ptr [val2]  
addsd       xmm0,mmword ptr [val3]  
addsd       xmm1,mmword ptr [val3]  
addsd       xmm0,mmword ptr [val4]  
addsd       xmm1,mmword ptr [val4]  
addsd       xmm0,mmword ptr [val5]  
addsd       xmm1,mmword ptr [val5]  
addsd       xmm0,mmword ptr [val6]  
addsd       xmm1,mmword ptr [val6]  
addsd       xmm0,mmword ptr [val7]  
addsd       xmm1,mmword ptr [val7]  
addsd       xmm0,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm0  
// Here we have to wait for resval to be uppdated!
addsd       xmm1,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm1

我并不是说它是乱序执行,但我当然可以看到循环如何比你的循环更快地执行。如果你有一个备用寄存器,你可能可以在你的汇编代码中实现同样的事情[在 x86_64 中你确实有另外 8 个寄存器,尽管你不能在 x86_64 中使用内联汇编...]

(请注意,寄存器重命名与我的“线程”循环不同,后者使用两个不同的寄存器 - 但效果大致相同,循环可以在命中“resVal”更新后继续,而无需等待结果更新)

于 2013-03-11T21:51:57.820 回答
0

可能对您不使用 _asm 很有用,但内在函数和内在类型(如 __m128d 女巫的 __m128i)代表 sse 寄存器。请参阅 immintrin.h,它定义了类型和许多 sse 函数。您可以在这里找到它们的良好描述和规范:http: //software.intel.com/sites/landingpage/IntrinsicsGuide/

于 2013-12-19T09:57:53.613 回答