对于非常接近1的基数,std::pow()非常慢

Very slow std::pow() for bases very close to 1

本文关键字:非常 std pow 于非常 接近      更新时间:2023-10-16

我有一个求解方程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。如本文所述,这不仅是我的问题(即,其他人也很生气),仅出现在x64上,并且仅适用于接近1.0x。类似的事情也发生在exp上。

解决这个问题对我来说至关重要。有人知道是否有办法绕过这种缓慢吗?

编辑:约翰指出,这是由于非标准化。那么问题是,如何解决这个问题呢?代码是C++,使用g++编译以在GNU Octave中使用。看起来,尽管我已经将CXXFLAGS设置为包括-mtune=native-ffast-math,但这并没有帮助,代码运行也同样缓慢。

现在的伪解决方案:对于所有关心这个问题的人来说,下面建议的解决方案对我个人来说并不奏效。我真的需要std::pow()的正常速度,但不要像x = 1那样迟缓。对我个人来说,解决方案是使用以下破解:

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,这已经足够好了。从我的发现来看,开销是最小的,因此这为我解决了问题。

显而易见的解决方法是注意,在reals中,a ** b == exp(log(a) * b)并使用该形式。你需要检查它是否会对你的结果的准确性产生不利影响。编辑:正如所讨论的,这也在很大程度上受到了经济放缓的影响。

问题不在于非规范化,至少不是直接的;尝试计算exp(-2.4980018054066093e-15)会遇到同样的减速,并且-2.4980018054066093e-15肯定不是非正规的。

如果你不在乎结果的准确性,那么缩放exponend或指数应该会让你走出慢区:

sqrt(pow(a, b * 2))
pow(a * 2, b) / pow(2, b)
...

glibc维护人员知道这个错误:http://sourceware.org/bugzilla/show_bug.cgi?id=13932-如果你正在寻找一个解决方案,而不是一个变通方案,你会想聘请一位具有开源经验的浮点数学专家。

64位Linux?

使用来自FreeBSD的pow()代码。

对于某些输入,Linux C库(glibc)在最坏的情况下具有糟糕的性能。

请参阅:http://entropymine.com/imageworsener/slowpow/

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

你没有提及你的收敛标准。也许这些也需要调整。