非常慢的std::pow()在底数非常接近1时

5
我有一个数值代码,解决方程f(x) = 0,其中我必须将x提高到幂p。我使用一些方法来解决它,但最终我使用的是牛顿法。解恰好等于x = 1,因此成为我的问题的原因。当迭代解接近1时,比如x = 1 + 1e-13,计算std::pow(x, p)所需的时间增长非常快,容易增加100倍,使我的代码无法使用。
运行此程序的机器是CentOS上的AMD64(Opteron 6172),命令很简单:y = std::pow(x, p);。在所有x64机器上都会出现类似的行为。正如此处所述,这不仅是我的问题(即其他人也很生气),而且仅适用于x接近1.0的情况。类似的事情也会发生在exp中。
解决这个问题对我至关重要。有没有人知道是否有任何方法可以避开这种缓慢?
编辑:John指出这是由于denormals引起的。问题是如何解决这个问题?该代码是C++编写的,使用g++编译,用于在GNU Octave中使用。虽然我已经将CXXFLAGS设置为包括-mtune=native-ffast-math,但似乎没有帮助,代码运行速度仍然很慢。
现在的伪解决方案:对于所有关心此问题的人,下面建议的解决方案对我个人而言都不起作用。我确实需要std::pow()的通常速度,但在x = 1附近却不会缓慢。对我个人而言,解决方案是使用以下hack:
inline double mpow(double x, double p) __attribute__ ((const));

inline double mpow(double x, double p)
{
    double y(x - 1.0);
    return (std::abs(y) > 1e-4) ? (std::pow(x, p)) : (1.0 + p * y * (1.0 + (p - 1.0) * y * (0.5 + (1.0 / 6.0) * (p - 2.0) * y)));
}

这个界限可更改,但对于 -40 < p < 40 的范围,误差小于约1e-11,已足够良好。根据我的发现,开销很小,因此这对我来说解决了问题。


5
这可能与次正规化数的一般性能问题有关。与浮点值非常接近0的计算可能比正常计算慢100倍。请参见https://dev59.com/yGox5IYBdhLWcg3wSCRW。 - John Kugelman
好的观点。有什么建议来解决这个问题吗?如果数字足够接近,将其修正为精确的1? - fledgling Cxx user
@JohnKugelman:如果你阅读了链接,这是因为glibc在给定某些输入值时使用了一个更慢的函数(名为__slowpow)。 - interjay
请参见https://dev59.com/q2ox5IYBdhLWcg3wVS4J。 - ecatmur
-ffast-math 会破坏 IEEE754 规范。这是否真的很糟糕还是可以接受,取决于您的用例,但如果我是您,我会在启用该标志之前进行进一步的研究以了解它正在做什么。 - us2012
我想我只需要flush-to-zero。该代码使用GCC(即g++)编译,用于在Octave内部使用;有什么办法可以强制它执行FTZ吗?恐怕添加-ffast-math并没有真正帮助。使用-O3 -march=native -mtune=native也没有什么作用。 - fledgling Cxx user
3个回答

9
显而易见的解决方法是注意在实数中,a ** b == exp(log(a) * b),并使用该形式。您需要检查它不会对结果的准确性产生不良影响。编辑:正如讨论的那样,这种方法也会受到相同程度的减速影响。
问题不在于非规格化浮点数,至少不是直接的原因;尝试计算exp(-2.4980018054066093e-15)也会遭遇同样的减速,而-2.4980018054066093e-15肯定不属于非规格化浮点数。
如果您不关心结果的准确性,则缩放指数或底数均可将其移出减速区域:
sqrt(pow(a, b * 2))
pow(a * 2, b) / pow(2, b)
...

这个错误已知于glibc维护者:http://sourceware.org/bugzilla/show_bug.cgi?id=13932 - 如果你想要修复而不是解决方法,你需要委托一位有开源经验的浮点数学专家。

在我的测试中,扩大xp并没有帮助。修复glibc中的问题也无济于事,因为这个东西必须运行在Mac OS和MATLAB上,后者使用古老的GCC来编译其MEX文件。 - fledgling Cxx user

1

0

这可能与你的算法有关。也许改用BFGS而不是牛顿法会有所帮助。

你没有提及收敛标准,也许需要进行调整。


他没有实现pow函数,而是使用了标准库的实现。 :) - jalf
1
不,这实际上正是问题所在。我已经计时了代码并尝试了所有可能直到找出原因。 - fledgling Cxx user
我明白了。BFGS与您构建矩阵的方式有关,而不一定与幂计算有关。 - duffymo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接