现在在 x86-64 上仍然值得使用 Quake 快速平方根反比算法吗?

Is it still worth using the Quake fast inverse square root algorithm nowadays on x86-64?

具体来说,这是我正在谈论的代码:

float InvSqrt(float x) {
  float xhalf = 0.5f*x;
  int i = *(int*)&x;        // warning: strict-aliasing UB, use memcpy instead
  i = 0x5f375a86- (i >> 1);
  x = *(float*)&i;          // same
  x = x*(1.5f-xhalf*x*x);
  return x;  
}

我忘记了我从哪里得到的,但它显然比原始的 Quake III 算法更好、更有效或更精确(魔法常数略有不同),但自创建该算法以来已有 2 多年,我只是想知道它是否仍然值得在性能方面使用它,或者是否有一条指令已经在现代 x86-64 CPU 中实现了它。

起源:

John Carmack's Unusual Fast Inverse Square Root (Quake III)


现代实用性:none,已被 SSE1 淘汰rsqrtss

使用 _mm_rsqrt_psss 得到非常近似的 reciprocal-sqrt 4 个并行浮点数,甚至比一个好的编译器用它做的要快得多(使用 SSE2 整数 shift/add 指令将 FP 位模式保存在 XMM 寄存器中,这可能是 而不是 它实际上如何将 type-pun 编译为整数。这是 strict-aliasing C 或 C++ 中的 UB;使用 memcpy 或 C++20 std::bit_cast.)

https://www.felixcloutier.com/x86/rsqrtss documents the scalar version of the asm instruction, including the |Relative Error| ≤ 1.5 ∗ 2−12 guarantee. (i.e. about half the mantissa bits are correct.) One Newton-Raphson iteration can refine it to within 1ulp of being correct, although still not the 0.5ulp you'd get from actual sqrt. See )

rsqrtps 在大多数 CPU 上的执行速度仅比 mulps / mulss 指令稍慢,例如 5 周期延迟,1/时钟吞吐量。 (通过 Newton 迭代对其进行优化,获得更多微指令。)延迟因微体系结构而异,在 Zen 3 中低至 3 微指令,但至少自 Conroe 以来英特尔以大约 5c 的延迟运行它 (https://uops.info/)。

整数移位/从 Quake InvSqrt 中的幻数中减去类似地证明ide更粗糙 initial-guess,其余的(在 type-pun 宁 bit-pattern 回到 float 是 Newton Raphson 迭代。


在使用 -ffast-math 编译 sqrt 时,编译器甚至会为您使用 rsqrtss,具体取决于上下文和调整选项。 (例如,用 -O3 -ffast-math -march=skylake https://godbolt.org/z/fT86bKesb 编译 1.0f/sqrtf(x) 的现代 clang 使用 vrsqrtss 和 3x vmulss 加上一个 FMA。) Non-reciprocal sqrt 通常不值得,但 rsqrt +细化避免了 division 和 sqrt.


Full-precision 平方根和 division 本身并不像以前那么慢,至少与 mul/add/sub 相比,如果你不经常使用它们的话。 (例如,如果你可以 hide 延迟,每 12 个左右的一个 sqrt 其他操作的成本可能大致相同,对于 rsqrt + Newton 迭代仍然是一个 uop 而不是多个。)见 Floating point division vs floating point multiplication
但是 sqrt 和 div 确实相互竞争吞吐量,因此需要 divide 平方根是一个令人讨厌的情况。

因此,如果您对一个主要只执行 sqrt,而不与其他数学运算混合的数组有一个错误的循环,那么 _mm_rsqrt_ps 的 use-case(以及牛顿迭代)作为更高吞吐量近似于 _mm_sqrt_ps

但是,如果您 可以 将该过程与其他东西结合起来以增加计算强度并在保持 div/sqrt 单元的同时完成更多工作,通常最好使用一个真正的 sqrt 指令本身,因为对于 front-end 发出,以及 back-end 跟踪和执行仍然只有 1 uop。如果 FMA 可用于平方根倒数,则牛顿迭代需要大约 5 微秒,否则更多(如果需要 non-reciprocal sqrt)。

以 Skylake 为例,每 3 个周期有 1 个 sqrtps xmm 吞吐量(128 位向量),如果您不执行多个操作,它的成本与 mul/add/sub/fma 操作相同每 6 次数学运算。 (256 位 YMM 向量的吞吐量更差,6 个周期。)牛顿迭代将花费更多的 uops,因此如果端口 0/1 的 uops 是瓶颈,直接使用 sqrt 是一个胜利。 (这是假设 out-of-order exec 可以 hide 延迟,通常是在每个循环迭代都是独立的情况下。)如果您使用多项式近似作为诸如 log 之类的东西的一部分,这种情况很常见或 exp 循环。

另见 回复:现代 OoO exec CPU 的性能。