pow(x, 0.5f) 的快速实现是不是比快速 sqrt(x) 快?

Posted

技术标签:

【中文标题】pow(x, 0.5f) 的快速实现是不是比快速 sqrt(x) 快?【英文标题】:Is fast implementation of pow(x, 0.5f) faster than fast sqrt(x)?pow(x, 0.5f) 的快速实现是否比快速 sqrt(x) 快? 【发布时间】:2012-08-04 17:43:58 【问题描述】:

我想知道 pow() 的快速实现,例如this one,是否比快速 sqrt(x) 更快地获得整数的平方根。我们知道

sqrt(x) = pow(x, 0.5f)

我自己无法测试速度,因为我没有找到 sqrt 的快速实现。 我的问题是:pow(x, 0.5f) 的快速实现是否比快速 sqrt(x) 快?

编辑:我的意思是 powf - pow 采用浮点数而不是双精度数。 (双打更容易误导)

【问题讨论】:

那个实现是一个近似值,这意味着它比使用 sqrt 有更高的错误,这就是它可以更快的原因。 更改为单精度参数和返回类型会更改我下面答案中的数字:pow 近似需要 9 个周期而不是 6 个(它被编写为对双精度进行操作,因此必须转换类型;可能它可以为浮点数重写),powf 需要 16 个周期而不是 29 个,sqrt 近似需要 7 个而不是 10 个(相反的效果,它是为浮点数编写的,所以类型转换消失了),sqrtf 需要 16 个而不是 29 个。 数字0.5可以用IEEE浮点数精确表示,所以允许编译器为你重写pow(x, 0.5)sqrt(x),C库允许为return sqrt(x)当第二个参数为 0.5 时,来自 pow 内部。我不知道有什么实现可以做这些事情,但如果知道有一个,我不会感到惊讶。 【参考方案1】:

关于C标准库sqrtpow,答案是

首先,如果pow(x, .5f)sqrt(x) 的实现更快,那么负责维护sqrt 的工程师将用pow(x, .5f) 替换实现。

其次,商业库中的 sqrt 实现通常专门针对执行该任务进行了优化,通常由熟悉编写高性​​能软件并使用或接近汇编语言编写以获得处理器可用性能的人员进行优化。

第三,许多处理器都有执行 sqrt 或协助计算它的指令。 (通常,有一条指令可以提供平方根倒数的估计值,并有一条指令来细化该估计值。)

然而

您链接的代码/您提出的问题是关于使用粗略近似的 pow 尝试粗略近似 sqrt

我将问题中提到的 pow 近似例程的最终版本转换为 C,并在计算 pow(3, .5) 时测量了它的运行时间。我还测量了系统 (Mac OS X 10.8) pow 和 sqrt 以及 the sqrt approximation here 的运行时间(一次迭代并乘以最后的参数以获得平方根,而不是它的倒数)。

首先,计算结果:pow 近似返回 1.72101。 sqrt 近似值返回 1.73054。系统 pow 和 sqrt 返回的正确值是 1.73205。

在MacPro4,1上以64位模式运行,pow近似需要6个周期,系统pow需要29个周期,平方根近似需要10个周期,系统sqrt需要29个周期。这些时间可能包括加载参数和存储结果的一些开销(我使用 volatile 变量来强制编译器不要优化掉其他无用的循环迭代,以便我可以测量它们)。

(这些时间是“有效吞吐量”,实际上是从一个调用开始到另一个调用可以开始的 CPU 周期数。)

【讨论】:

我写了上面的代码来比较 sqrt 和典型库中的 pow。但是,问题要求我们将 sqrt 与 pow 近似值进行比较。在这种情况下,(非常糟糕的)pow 近似值在某些平台上可能会超过 sqrt。但是,请注意,pow 近似声称典型误差为 5% 到 12%。典型 sqrt 实现中的误差通常在 0.000000000000222% 左右。所以这不是一个公平的比较。 确实如此。我在回答中考虑到了这一点,但我会对其进行编辑以使其更清晰。 ...如果愿意牺牲准确性,直接近似sqrt( ) 会更快。 逼近sqrt 错误是微不足道的。只需对浮点表示的位进行操作,将指数减半,然后对尾数进行廉价修复......【参考方案2】:

在MSVC++ 2013 64位模式下运行以下代码的结果,全面优化。 sqrt() 的性能约为 9 倍;

距离为2619435809228.278300

Pow() 经过的时间是 18413.000000 毫秒

距离为2619435809228.278300

Sqrt() 经过的时间是 2002.000000 毫秒

#define LOOP_KNT 249000000  // (SHRT_MAX * 1024)

int main(void)    
    time_t start = clock();

    double distance = 0, result = 0;
    start = clock();
    for(int i=0; i<LOOP_KNT; i++) 
        result = pow(i, 0.50);
        distance += result;
    
    printf("\nDistance is %f", distance);
   printf("\nPow() elapsed time was %f milliseconds", (double)clock() - (double)(start));

   distance = 0, result = 0;
   start = clock();
    for(int i=0; i<LOOP_KNT; i++) 
        result = sqrt(i);
        distance += result;
    
    printf("\nDistance is %f", distance);
    printf("\nSqrt() elapsed time was %f milliseconds", (double)clock() - (double)(start));

   printf("\nHit any key to end program.\n");
   getchar();

   return 0;

无需绞尽脑汁、理论化或夸夸其谈。只需编写基准并观察结果。

【讨论】:

感谢您的回答;但是标准库中的sqrtpow 都非常慢。 注意:在我的 Cygwin 64 位 PC 上尝试了相同的操作 - 比率 1.04。 pow() vs sqrt(). @Zaffy,关闭只计入马蹄铁和手榴弹。 25% 的错误会使您的链接方法毫无价值。它也是用 Java 编写的,因此一开始就性能不佳。 “这真的非常紧凑。计算只需要 2 次移位,1 次 mul,2 次加法和 2 次寄存器操作。就是这样!在我的测试中,它通常在 5% 到 12% 的误差范围内,在极端情况下有时高达25%。”【参考方案3】:

一般来说,给定相同的误差约束,更具体的问题可以比更一般的问题更优化。

因此,您可以采用该算法,并将 b 替换为常数 0.5,现在您的 sqrt() 至少与 pow() 一样快。现在它是恒定的,编译器(或人类)可以基于它进行优化。

请注意,pow() 函数是一个近似值,具有(相对)大的误差,因此不如说的大多数库 sqrt 函数准确。如果您将 sqrt 的实现放宽到相同的近似限制,您确实可以让它至少一样快。

【讨论】:

sqrt 是代数函数,而 pow() 是超越函数,但实际上它们都是近似值,通常是 Newton-Raphson 逐次逼近。 sosmath.com/calculus/diff/der07/der07.html 良好的sqrt() 实现可以证明精确到 0.5 ULP 以内。 pow() 很少有可证明的准确性。好的实现通常会在 1 个 ULP 内返回结果。

以上是关于pow(x, 0.5f) 的快速实现是不是比快速 sqrt(x) 快?的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode50. Pow(x, n)(快速幂)

50. Pow(x, n)-快速幂+递归

LeetCode 50. Pow(x, n)(快速幂,Java)

快速定点 pow、log、exp 和 sqrt

hdu 1061Rightmost Digit(水题 快速幂 分治)

leetcode 50 Pow(x,n) 快速幂