首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是否可以在Rust中编写Quake的快速InvSqrt()函数?

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

Stack Overflow用户
提问于 2019-11-28 12:45:28
回答 3查看 8.4K关注 0票数 107

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

有没有这样的实现:

代码语言:javascript
复制
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;
}

在铁锈?如果存在,请发布代码。

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

代码语言:javascript
复制
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
}

参考资料:

  1. Origin of Quake3's Fast InvSqrt() - Page 1
  2. Understanding Quake’s Fast Inverse Square Root
  3. FAST INVERSE SQUARE ROOT.pdf
  4. source code: q_math.c#L552-L572
EN

回答 3

Stack Overflow用户

发布于 2019-11-28 15:40:31

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

有一个函数可以做到这一点:,它返回一个u32。还有一个用于另一个方向的函数:,它接受u32作为参数。这些函数比mem::transmute更受欢迎,因为后者是unsafe,使用起来比较麻烦。

下面是InvSqrt的实现

代码语言:javascript
复制
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)
}

(Playground)

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

代码语言:javascript
复制
.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

我还没有找到任何引用程序集(如果你有,请告诉我!),但它对我来说似乎相当不错。我只是不确定为什么把浮点数移到eax中只是为了做移位和整数减法。也许SSE寄存器不支持这些操作?

带有-O3的Clang9.0将C代码编译成basically the same assembly。所以这是个好兆头。

值得指出的是,如果你真的想在实践中使用这一点:请不要这样做。作为benrg pointed out in the comments,现代x86 CPU有一个专门的指令来实现这个功能,它比这个技巧更快更准确。不幸的是,1.0 / x.sqrt() does not seem to optimize to that instruction。因此,如果您确实需要速度,那么使用the _mm_rsqrt_ps intrinsics可能是最佳选择。然而,这同样需要unsafe代码。我不会在这个答案中深入讨论太多细节,因为只有少数程序员会真正需要它。

票数 95
EN

Stack Overflow用户

发布于 2019-11-28 13:23:14

这是在Rust中使用鲜为人知的union实现的:

代码语言:javascript
复制
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)
    }
}

在x86-64linux机器上使用criterion机箱做了一些微基准测试。令人惊讶的是,Rust自己的sqrt().recip()是最快的。但当然,任何微观基准测试结果都应该持保留态度。

代码语言:javascript
复制
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]
票数 41
EN

Stack Overflow用户

发布于 2019-11-28 13:05:06

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

代码语言:javascript
复制
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
}

你可以在这里找到一个活的例子:here

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59081890

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档