使用XMM寄存器和内存获取(c++代码)比只使用XMM寄存器的ASM快两倍——为什么?
Using XMM0 register and memory fetches (C++ code) is twice as fast as ASM only using XMM registers - Why?
我正在尝试实现一些内联汇编器(在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;
my ASM code:
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++代码性能较低?
- 一旦数据在缓存中(这将是在第一个循环之后的情况,如果它还没有在那里),使用内存或寄存器没有什么区别。
- 首先,浮点数添加将花费比单个周期稍长的时间。
-
resVal
的最后一个存储"unties"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"更新后继续,而不必等待结果更新)
可能对你不使用_asm有用,但是内部函数和内部类型(如__m128i的__m128d witch)表示sse寄存器。参见imminting .h,它的定义类型和许多sse函数。你可以在这里找到很好的描述和规范:http://software.intel.com/sites/landingpage/IntrinsicsGuide/
- 本质:使用__128寄存器
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 在模拟器中使用并集来模拟CPU寄存器有多合适
- 使用英特尔 PIN 修改寄存器
- AVX 指令中寄存器和指针之间的客观差异
- 如何确定我的处理器有多少个 AVX 寄存器?
- 除非使用某些寄存器,否则函数挂钩会崩溃
- 寄存器上的管道计算
- 其中关于内存和寄存器的左值和右值
- 有没有办法强制C++编译器将变量存储在寄存器中?
- "变量":函数中函数作用域不允许初始化的自动或寄存器变量'naked'
- xmm 寄存器中的__m128何时?
- 在英特尔x86体系结构上使用非AVX指令修改xmm整数寄存器值
- 有没有一种方法可以利用所有的XMM寄存器
- 检查 XMM 寄存器中是否有所有零
- 是阻塞xmm/ymm寄存器的静态/静态本地SSE/AVX变量
- 如何将两组4短裤加载到XMM寄存器中
- 为什么编译器对原始/std数组使用XMM寄存器,而对向量不使用?
- 使用XMM寄存器和内存获取(c++代码)比只使用XMM寄存器的ASM快两倍——为什么?
- 为什么gcc/clang使用两个128位的xmm寄存器来传递单个值