SSE归一化比简单逼近慢?

时间:2011-02-01 19:12:36

标签: c++ normalization profile sse approximation

我正在尝试将4d矢量标准化。

我的第一个approch是使用SSE内在函数 - 这为我的矢量算法提供了2倍的速度提升。 这是基本代码:( v.v4是输入)(使用GCC)(所有内容都是内联的)

//find squares
v4sf s = __builtin_ia32_mulps(v.v4, v.v4);
//set t to square
v4sf t = s;
//add the 4 squares together
s   = __builtin_ia32_shufps(s, s, 0x1B);
t      = __builtin_ia32_addps(t, s);
s   = __builtin_ia32_shufps(s, s, 0x4e);
t      = __builtin_ia32_addps(t, s);
s   = __builtin_ia32_shufps(s, s, 0x1B);
t      = __builtin_ia32_addps(t, s);
//find 1/sqrt of t
t      = __builtin_ia32_rsqrtps(t);
//multiply to get normal
return Vec4(__builtin_ia32_mulps(v.v4, t));

我检查了反汇编,它看起来像我期待的那样。我没有看到任何大问题。

无论如何,然后我尝试使用近似值:(我从谷歌得到了这个)

float x = (v.w*v.w) + (v.x*v.x) + (v.y*v.y) + (v.z*v.z);
float xhalf = 0.5f*x;
int i = *(int*)&x; // get bits for floating value
i = 0x5f3759df - (i>>1); // give initial guess y0
x = *(float*)&i; // convert bits back to float
x *= 1.5f - xhalf*x*x; // newton step, repeating this step
// increases accuracy
//x *= 1.5f - xhalf*x*x;
return Vec4(v.w*x, v.x*x, v.y*x, v.z*x);

运行速度略快于SSE版本! (大约快5-10%)它的结果也非常准确 - 我会说找到长度时为0.001! 但是...... GCC正在给我那种蹩脚的严格混淆规则,因为它有类型惩罚。

所以我修改它:

union {
    float fa;
    int ia;
};
fa = (v.w*v.w) + (v.x*v.x) + (v.y*v.y) + (v.z*v.z);
float faHalf = 0.5f*fa;
ia = 0x5f3759df - (ia>>1);
fa *= 1.5f - faHalf*fa*fa;
//fa *= 1.5f - faHalf*fa*fa;
return Vec4(v.w*fa, v.x*fa, v.y*fa, v.z*fa);

现在修改后的版本(没有警告)运行速度较慢 !!它的运行速度几乎是SSE版本的60%(但结果相同)!这是为什么?

所以这是问题:

  1. 我的SSE实施是否正确?
  2. SSE真的比普通的fpu操作慢吗?
  3. 为什么第三代码到底要慢得多?

3 个答案:

答案 0 :(得分:2)

我是一个涂料 - 我意识到我在基准测试时运行了SETI @ Home。我猜这是在扼杀我的SSE表现。把它关掉,让它以两倍的速度运行。

我也在AMD Athlon上进行了测试并获得了相同的结果 - SSE更快。

至少我修复了shuf bug!

答案 1 :(得分:1)

这是我能想到的最有效的汇编代码。您可以将其与编译器生成的内容进行比较。假设输入和输出在XMM0中。

       ; start with xmm0 = { v.x v.y v.z v.w }
       movaps  %xmm0, %mm1         ; save it till the end
       mulps   %xmm0, %xmm0        ; v=v*v
       pshufd  $1, %xmm0, %xmm1    ; xmm1 = { v.y v.x v.x v.x }
       addss   %xmm0, %xmm1        ; xmm1 = { v.y+v.x v.x v.x v.x }
       pshufd  $3, %xmm0, %xmm2    ; xmm2 = { v.w v.x v.x v.x }
       movhlps %xmm0, %xmm3        ; xmm3 = { v.z v.w ? ? }
       addss   %xmm1, %xmm3        ; xmm3 = { v.y+v.x+v.z v.x ? ? }
       addss   %xmm3, %xmm2        ; xmm2 = { v.y+v.x+v.z+v.w v.x v.x v.x }
       rsqrtps  %xmm2, %xmm1        ; xmm1 = { rsqrt(v.y+v.x+v.z+v.w) ... }
       pshufd  $0, %xmm1, %xmm1    ; xmm1 = { rsqrt(v.y+v.x+v.z+v.w) x4 }
       mulps   %xmm1, %xmm0       
       ; end with xmm0 = { v.x*sqrt(...) v.y*sqrt(...) v.z*sqrt(...) v.w*sqrt(...) }

答案 2 :(得分:0)

我的猜测是第3版本较慢,因为编译器决定将union放在内存变量中。在转换的情况下,它可以将值从寄存器复制到寄存器。您只需查看生成的机器代码即可。

至于为什么SSE不准确,我没有答案。如果你能给出真实的数字会有所帮助。如果在大小为1的向量上差异为0.3,那将是无耻的。