我正在尝试实现一些内联汇编程序(在Visual Studio 2012 C ++代码中)以利用SSE。 我想添加7个数字1e9次,所以我把它们从RAM放到xmm0到xmm6的CPU寄存器中。当我使用此代码在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 ++代码性能较低?
答案 0 :(得分:10)
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”更新后继续,而不必等待结果要更新)
答案 1 :(得分:0)
可能对你不使用_asm很有用,但内在函数和像__m128d的__m128i这样的内在类型代表sse寄存器。请参阅immintrin.h,它定义了类型和许多sse函数。你可以在这里找到好的描述和规格:http://software.intel.com/sites/landingpage/IntrinsicsGuide/