【问题标题】:SSE normalization slower than simple approximation?SSE 归一化比简单近似慢?
【发布时间】:2011-06-19 12:14:24
【问题描述】:

我正在尝试规范化 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. 为什么第三个代码这么慢?

【问题讨论】:

  • 了解您使用的 CPU 会有所帮助。例如。旧的 x86 CPU(Core 2 之前)的 SSE 功能非常差。
  • @Pepe:较旧的 x86 CPU 只有 64 位 SSE 执行单元(128 位操作被分解为两个 64 位微操作),因此您通常不会看到浮点 SIMD 与标量相比有多少速度提升代码。在较新的 CPU 上尝试您的代码 - 例如Core 2 Duo,Core i5/i7,具有完整的 128 位 SSE 单元。
  • @Suma:是的,我很困惑,并认为 shuf 是循环携带。我修复了它,现在它是准确的,但仍然没有更快!我将在另一台计算机上尝试。
  • 我认为您应该能够使用两次随机播放和两个加法来对所有四个分量求和:1,2,3,4 随机播放到 2,2,4,4 加到 1+2,x , 3+4,x 洗牌到 3+4,x,3+4,x,加到 1+2+3+4,x,x,x。然后你可以使用 scalar builtin_ia32_rsqrtss,将它打乱到所有槽中,最后相乘。

标签: c++ normalization profile sse approximation


【解决方案1】:

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

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

【讨论】:

  • x87 fpu 更准确,因为它在内部使用 80 位浮点值进行计算。
【解决方案2】:

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

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

至少我修复了 shuf 错误!

【讨论】:

    【解决方案3】:

    这是我能想到的最有效的汇编代码。您可以将其与编译器生成的内容进行比较。假设输入和输出在 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(...) }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-09
      • 2021-03-25
      • 2011-04-08
      • 2012-03-22
      • 2017-06-10
      • 1970-01-01
      • 2014-06-23
      • 1970-01-01
      相关资源
      最近更新 更多