快速1 / X除法(倒数)

时间:2012-03-30 08:19:52

标签: c++ performance

如果精度不重要,有没有办法在速度方面改善倒数(在X上除1)?

所以,我需要计算1 / X.是否有一些解决方法,所以我失去了精度,但做得更快?

6 个答案:

答案 0 :(得分:7)

我相信他所寻找的是一种更有效的近似1.0 / x的方法,而不是近似的一些技术定义,表明你可以使用1作为一个非常强烈的答案。我也相信这满足了这一点。

__inline__ double __attribute__((const)) reciprocal( unsigned long long x ) {
    //The type is unsigned long long, but you are restricted to a max value of 2^32-1, not
    // 2^64-1 like the unsigned long long is capable of storing
    union {
        double dbl;
        unsigned long long ull;
    } u = {.dbl=(x*=x)};        // x*x = pow( x, 2 )
    u.ull = ( 0xbfcdd6a18f6a6f52ULL - u.ull ) >> (unsigned char)1;
                                // pow( pow(x,2), -0.5 ) = pow( x, -1 ) = 1.0 / x
                                // This is done via the 'fast' inverse square root trick
    return u.dbl;
}


__inline__ double __attribute__((const)) reciprocal( double x ) {
    union {
        double dbl;
        unsigned long long ull;
    } u;
    u.dbl = x;
    u.ull = ( 0xbfcdd6a18f6a6f52ULL - u.ull ) >> (unsigned char)1;
                                    // pow( x, -0.5 )
    u.dbl *= u.dbl;                 // pow( pow(x,-0.5), 2 ) = pow( x, -1 ) = 1.0 / x
    return u.dbl;
}


__inline__ float __attribute__((const)) reciprocal( float x ) {
    union {
        float dbl;
        unsigned uint;
    } u;
    u.dbl = x;
    u.uint = ( 0xbe6eb3beU - u.uint ) >> (unsigned char)1;
                                    // pow( x, -0.5 )
    u.dbl *= u.dbl;                 // pow( pow(x,-0.5), 2 ) = pow( x, -1 ) = 1.0 / x
    return u.dbl;
}


嗯.......如果CPU制造商知道你可以在设计CPU时只用一次乘法,减法和位移来获得倒数,那我就更聪明......嗯........

对于基准测试,硬件x 2 指令与硬件减法指令相结合,与现代计算机上的硬件1.0 / x指令一样快(我的基准测试是在Intel i7上进行的,但我会假设其他处理器的结果相似)。但是,如果将此算法作为新的汇编指令实现到硬件中,则速度的提高可能足以使该指令非常实用。

有关此方法的详细信息,此实现基于精彩的"fast" inverse square root algorithm

最后,请注意我在C ++中更像是一个新手。因此,我欢迎任何最佳实践,正确格式化或暗示清晰的编辑,以便为所有阅读它的人提高答案质量。

答案 1 :(得分:5)

首先,确保这不是过早优化的情况。你知道这是你的瓶颈吗?

正如神秘所说,1 / x可以很快计算出来。确保您没有将double数据类型用于1或除数。浮动速度要快得多。

那就是基准,基准,基准。不要浪费你的时间花在数值理论上只是为了发现性能不佳的来源是IO访问。

答案 2 :(得分:3)

首先,如果打开编译器优化,编译器可能会尽可能优化计算(例如,将其拉出循环)。要查看此优化,您需要在发布模式下构建和运行。

除法可能比乘法更重(但是一位评论者指出倒数和现代CPU上的乘法一样快,在这种情况下,这对你的情况来说是不正确的),所以如果你确实有{{1}出现在循环中的某个地方(并且不止一次),您可以通过在循环(1/X)中缓存结果然后使用float Y = 1.0f/X;来协助。 (编译器优化在任何情况下都可以这样做。)

此外,可以重新设计某些公式以删除除法或其他低效计算。为此,您可以发布正在执行的较大计算。即使在那里,程序或算法本身有时也可以进行重组,以防止频繁点击耗时的循环。

可以牺牲多少准确度?如果有可能只需要一个数量级,您可以使用模数运算符或按位运算轻松获得。

然而,总的来说,没有办法加快分工。如果有,编译器就已经在做了。

答案 3 :(得分:1)

这应该通过一系列预先展开的牛顿迭代进行评估,并将其评估为使用融合乘法累加运算的Horner多项式,大多数现代CPU在单个Clk循环中执行(每次) :

float inv_fast(float x) {
    union { float f; int i; } v;
    float w, sx;
    int m;

    sx = (x < 0) ? -1:1;
    x = sx * x;

    v.i = (int)(0x7EF127EA - *(uint32_t *)&x);
    w = x * v.f;

    // Efficient Iterative Approximation Improvement in horner polynomial form.
    v.f = v.f * (2 - w);     // Single iteration, Err = -3.36e-3 * 2^(-flr(log2(x)))
    // v.f = v.f * ( 4 + w * (-6 + w * (4 - w)));  // Second iteration, Err = -1.13e-5 * 2^(-flr(log2(x)))
    // v.f = v.f * (8 + w * (-28 + w * (56 + w * (-70 + w *(56 + w * (-28 + w * (8 - w)))))));  // Third Iteration, Err = +-6.8e-8 *  2^(-flr(log2(x)))

    return v.f * sx;
}

精细打印:接近0,近似值不能很好,所以要么程序员需要测试性能,要么在求助于硬件除法之前将输入限制为低。 即负责!

答案 4 :(得分:1)

我已经在Arduino NANO上测试了这些方法的速度和“准确性”。
基本计算是设置变量,Y = 15,000,000,Z = 65,535
(在我的实际情况中,Y是一个常数,Z可以在65353和3000之间变化,因此是一个有用的测试)
Arduino上的计算时间是通过以下方式建立的:将引脚设置为低电平,然后将引脚设置为高电平,然后再次设置为低电平,然后与逻辑分析仪进行比较。 100个周期。 使用变量作为无符号整数:-

Y * Z takes 0.231 msec
Y / Z takes  3.867 msec.  
With variables as floats:-  
Y * Z takes  1.066 msec
Y / Z takes  4.113 msec.  
Basic Bench Mark  and ( 15,000,000/65535 = 228.885 via calculator.) 

使用{Jack Giffin's float float倒数算法:

Y * reciprocal(Z)  takes  1.937msec  which is a good improvement, but accuracy less so 213.68.  

使用{nimig18的} float inv_fast:

Y* inv_fast(Z)  takes  5.501 msec  accuracy 228.116  with single iteration  
Y* inv_fast(Z)  takes  7.895 msec  accuracy 228.883  with second iteration 

使用维基百科的Q_rsqrt(由{Jack Giffin}指向)

Y * Q*rsqrt(Z) takes  6.104 msec  accuracy   228.116  with single iteration  
All entertaining but ultimately disappointing!

答案 5 :(得分:0)

我所知道的最快的方法是使用SIMD操作。 http://msdn.microsoft.com/en-us/library/796k1tty(v=vs.90).aspx