6

我正在尝试规范化 4d 矢量。

我的第一个方法是使用 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. 为什么第三个代码这么慢?
4

3 回答 3

2

我是个笨蛋——我意识到我在进行基准测试时运行了 SETI@Home。我猜它正在扼杀我的 SSE 表现。将其关闭并使其运行速度提高一倍。

我还在 AMD athlon 上对其进行了测试,得到了相同的结果——SSE 更快。

至少我修复了 shuf 错误!

于 2011-02-01T20:10:55.680 回答
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(...) }
于 2011-02-01T21:46:07.733 回答
0

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

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

于 2011-02-01T19:31:19.210 回答