在Doom 3 BFG代码中计算Sqrt(x)为x*InvSqrt(x)是否有意义?

Rob*_*ger 44 c++ optimization math.h micro-optimization

我浏览了最近发布的Doom 3 BFG源代码,当时我遇到了一些似乎没有任何意义的东西.Doom 3在idMath类中包含数学函数.一些函数只是向相应的函数提供math.h,但有些是重新实现(例如idMath :: exp16()),我认为它具有比它们的math.h对应物更高的性能(可能以牺牲精度为代价).

然而,令我感到困惑的是他们实现这一float idMath::Sqrt(float x)功能的方式:

ID_INLINE float idMath::InvSqrt( float x ) {
     return ( x > FLT_SMALLEST_NON_DENORMAL ) ? sqrtf( 1.0f / x ) : INFINITY;
}

ID_INLINE float idMath::Sqrt( float x ) {
     return ( x >= 0.0f ) ? x * InvSqrt( x ) : 0.0f;
}
Run Code Online (Sandbox Code Playgroud)

这似乎执行两个不必要的浮点运算:首先是除法然后是乘法.

值得注意的是,原始的Doom 3源代码也以这种方式实现了平方根函数,但是反平方根使用了快速平方根算法.

ID_INLINE float idMath::InvSqrt( float x ) {

    dword a = ((union _flint*)(&x))->i;
    union _flint seed;

    assert( initialized );

    double y = x * 0.5f;
    seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<<EXP_POS) | iSqrt[(a >> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK];
    double r = seed.f;
    r = r * ( 1.5f - r * r * y );
    r = r * ( 1.5f - r * r * y );
    return (float) r;
}


ID_INLINE float idMath::Sqrt( float x ) {
    return x * InvSqrt( x );
}
Run Code Online (Sandbox Code Playgroud)

你看,在计算任何优势Sqrt(x)x * InvSqrt(x),如果InvSqrt(x)内部只是调用math.hfsqrt(1.f/x)?我可能在这里遗漏了一些关于非规范化浮点数的重要事项,还是这只是id软件的一部分?

Ben*_*ith 8

我可以看到这样做的两个原因:首先,"快速invSqrt"方法(实际上是Newton Raphson)现在是许多硬件中使用的方法,因此这种方法留下了利用这种硬件的可能性(和一次可能做四次或更多这样的操作).本文稍微讨论一下:

计算平方根有多慢(多少个周期)?

第二个原因是兼容性.如果更改用于计算平方根的代码路径,则可能会得到不同的结果(尤其是对于零,NaN等),并且与依赖于旧系统的代码失去兼容性.


ale*_*nis 5

据我所知,InvSqrt用于计算颜色的颜色取决于光从表面反射的角度,这使用平方根的倒数给出一些函数.

在他们的情况下,他们在计算这些数字时不需要很大的精度,因此Doom 3代码(最初来自Quake III)背后的工程师提出了一种非常非常快速的计算近似的InvSqrt方法,仅使用几次Newton-Raphson迭代.

这就是他们InvSqrt在所有代码中使用而不是使用内置(较慢)函数的原因.我想使用x * InvSqrt(x)是为了避免将工作乘以2(通过有两个非常有效的函数,一个用于InvSqrt,另一个用于Sqrt).

你应该阅读这篇文章,它可能会对这个问题有所了解.