是否可以在Rust中编写Quake的快速InvSqrt()函数?

Fly*_*lyq 62 floating-point sqrt type-punning rust

这只是为了满足我自己的好奇心。

是否有此实现:

float InvSqrt (float x)
{
   float xhalf = 0.5f*x;
   int i = *(int*)&x;
   i = 0x5f3759df - (i>>1);
   x = *(float*)&i;
   x = x*(1.5f - xhalf*x*x);
   return x;
}
Run Code Online (Sandbox Code Playgroud)

在Rust中?如果存在,则发布代码。

我尝试过但失败了。我不知道如何使用整数格式编码浮点数。这是我的尝试:

fn main() {
    println!("Hello, world!");
    println!("sqrt1: {}, ",sqrt2(100f64));
}

fn sqrt1(x: f64) -> f64 {
    x.sqrt()
}

fn sqrt2(x: f64) -> f64 {
    let mut x = x;
    let xhalf = 0.5*x;
    let mut i = x as i64;
    println!("sqrt1: {}, ", i);

    i = 0x5f375a86 as i64 - (i>>1);

    x = i as f64;
    x = x*(1.5f64 - xhalf*x*x);
    1.0/x
}
Run Code Online (Sandbox Code Playgroud)

参考:
1 . Quake3快速InvSqrt()的起源-第1页
2. 了解Quake快速逆平方根
3. FAST INVERSE SQUARE ROOT.pdf
4. 源代码:q_math.c#L552-L572

Luk*_*odt 62

我不知道如何使用整数格式编码浮点数。

有一个函数:f32::to_bits返回一个u32。还有另一个方向的函数:f32::from_bits它以u32as作为参数。这些功能优于mem::transmute后者,因为后者unsafe难以使用。

这样,这里是实现InvSqrt

fn inv_sqrt(x: f32) -> f32 {
    let i = x.to_bits();
    let i = 0x5f3759df - (i >> 1);
    let y = f32::from_bits(i);

    y * (1.5 - 0.5 * x * y * y)
}
Run Code Online (Sandbox Code Playgroud)

游乐场


此函数在x86-64上编译为以下程序集:

.LCPI0_0:
        .long   3204448256        ; f32 -0.5
.LCPI0_1:
        .long   1069547520        ; f32  1.5
example::inv_sqrt:
        movd    eax, xmm0
        shr     eax                   ; i << 1
        mov     ecx, 1597463007       ; 0x5f3759df
        sub     ecx, eax              ; 0x5f3759df - ...
        movd    xmm1, ecx
        mulss   xmm0, dword ptr [rip + .LCPI0_0]    ; x *= 0.5
        mulss   xmm0, xmm1                          ; x *= y
        mulss   xmm0, xmm1                          ; x *= y
        addss   xmm0, dword ptr [rip + .LCPI0_1]    ; x += 1.5
        mulss   xmm0, xmm1                          ; x *= y
        ret
Run Code Online (Sandbox Code Playgroud)

我没有找到任何参考程序集(如果有的话,请告诉我!),但是对我来说似乎还不错。我只是不确定为什么将浮点数移入eax以进行移位和整数减法。上交所寄存器可能不支持这些操作吗?

clang 9.0可以-O3将C代码编译为基本相同的程序集。这是一个好兆头。

  • @Gloweye这是我们谈论的另一种“不安全”。快速逼近与最佳点相距太远而产生的不良价值,与快速和松散且行为不确定的事物相比。 (8认同)
  • @Gloweye:从数学上来说,“ fast_inv_sqrt”的最后一部分只是一个牛顿-拉夫森迭代步骤,可以找到更好的“ inv_sqrt”近似值。这部分没有什么不安全的。窍门在第一部分,找到了一个很好的近似值。之所以起作用是因为它在float的指数部分进行了整数除以2,实际上是sqrt(pow(0.5,x))= pow(0.5,x / 2)。 (4认同)
  • 根据《英特尔技术指南》,没有整数移位运算只能将128位寄存器模拟的最低32位移位为“ addss”或“ mulss”。但是,如果可以忽略xmm0的其他96位,则可以使用“ psrld”指令。整数减法也是如此。 (2认同)

edw*_*rdw 28

union在Rust中鲜为人知的是实现了这一点:

union FI {
    f: f32,
    i: i32,
}

fn inv_sqrt(x: f32) -> f32 {
    let mut u = FI { f: x };
    unsafe {
        u.i = 0x5f3759df - (u.i >> 1);
        u.f * (1.5 - 0.5 * x * u.f * u.f)
    }
}
Run Code Online (Sandbox Code Playgroud)

是否criterion在x86-64 Linux盒子上使用板条箱进行了一些微基准测试。令人惊讶的是,Rust自己sqrt().recip()的速度最快。但是,当然,任何微基准测试结果都应一粒盐。

inv sqrt with transmute time:   [1.6605 ns 1.6638 ns 1.6679 ns]
inv sqrt with union     time:   [1.6543 ns 1.6583 ns 1.6633 ns]
inv sqrt with to and from bits
                        time:   [1.7659 ns 1.7677 ns 1.7697 ns]
inv sqrt with powf      time:   [7.1037 ns 7.1125 ns 7.1223 ns]
inv sqrt with sqrt then recip
                        time:   [1.5466 ns 1.5488 ns 1.5513 ns]
Run Code Online (Sandbox Code Playgroud)

  • 我毫不奇怪,`sqrt()。inv()`是最快的。现在,sqrt和inv都是单一指令,并且运行很快。Doom是在无法完全假设存在硬件浮点的时代编写的,而sqrt之类的先验功能*肯定是软件。基准为+1。 (14认同)
  • 令我惊讶的是,“ transmute”显然不同于“ to_”和“ from_bits”-我希望它们在优化之前就等同于指令。 (2认同)
  • @MartinBonner(另外,这并不重要,但 sqrt 不是一个[超越函数](https://en.wikipedia.org/wiki/Transcendental_function)。) (2认同)
  • @MartinBonner:任何支持除法的硬件FPU通常也会支持sqrt。需要IEEE“基本”运算(+-* / sqrt)才能产生正确的舍入结果;这就是为什么SSE提供所有这些操作,但不提供exp,sin或其他任何操作的原因。实际上,除法和sqrt通常在相同的执行单元上运行,设计方式类似。请参阅[HW div / sqrt单位详细信息](/sf/ask/3824986441/说唱)。无论如何,相乘还是比较快,特别是在延迟方面。 (2认同)

Dee*_*doo 5

您可以std::mem::transmute用来进行所需的转换:

fn inv_sqrt(x: f32) -> f32 {
    let xhalf = 0.5f32 * x;
    let mut i: i32 = unsafe { std::mem::transmute(x) };
    i = 0x5f3759df - (i >> 1);
    let mut res: f32 = unsafe { std::mem::transmute(i) };
    res = res * (1.5f32 - xhalf * res * res);
    res
}
Run Code Online (Sandbox Code Playgroud)

您可以在此处找到一个实时示例:这里

  • @Sahsahae我刚刚使用您提到的两个功能发布了答案:)我同意,此处应避免使用“不安全”,因为这是不必要的。 (5认同)
  • 不安全没有错,但是有一种方法可以在没有显式不安全块的情况下执行此操作,因此我建议使用[`f32 :: to_bits`](https://doc.rust-lang.org/std /primitive.f32.html#method.to_bits)和[`f32 :: from_bits`](https://doc.rust-lang.org/std/primitive.f32.html#method.from_bits)。它也明显不同于转换,而大多数人可能将其视为“魔术”。 (4认同)