pow()
对NaN值的灾难性表现是什么原因? As far as I can work out,如果使用SSE而不是x87 FPU进行浮点数学运算,则NaN不应对性能产生影响。
这似乎适用于基本操作,但不适用于pow()
。我将双倍的乘法和除法比作平方,然后取平方根。如果我使用g++ -lrt
编译下面的代码,我会得到以下结果:
multTime(3.14159): 20.1328ms
multTime(nan): 244.173ms
powTime(3.14159): 92.0235ms
powTime(nan): 1322.33ms
正如预期的那样,涉及NaN的计算需要相当长的时间。然而,使用g++ -lrt -msse2 -mfpmath=sse
进行编译会导致以下时间:
multTime(3.14159): 22.0213ms
multTime(nan): 13.066ms
powTime(3.14159): 97.7823ms
powTime(nan): 1211.27ms
NaN的乘法/除法现在要快得多(实际上比实数更快),但是平方和取平方根仍需要很长时间。
测试代码(在VMWare中使用32位OpenSuSE 10.2上的gcc 4.1.2编译,CPU是Core i7-2620M)
#include <iostream>
#include <sys/time.h>
#include <cmath>
void multTime( double d )
{
struct timespec startTime, endTime;
double durationNanoseconds;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &startTime);
for(int i=0; i<1000000; i++)
{
d = 2*d;
d = 0.5*d;
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &endTime);
durationNanoseconds = 1e9*(endTime.tv_sec - startTime.tv_sec) + (endTime.tv_nsec - startTime.tv_nsec);
std::cout << "multTime(" << d << "): " << durationNanoseconds/1e6 << "ms" << std::endl;
}
void powTime( double d )
{
struct timespec startTime, endTime;
double durationNanoseconds;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &startTime);
for(int i=0; i<1000000; i++)
{
d = pow(d,2);
d = pow(d,0.5);
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &endTime);
durationNanoseconds = 1e9*(endTime.tv_sec - startTime.tv_sec) + (endTime.tv_nsec - startTime.tv_nsec);
std::cout << "powTime(" << d << "): " << durationNanoseconds/1e6 << "ms" << std::endl;
}
int main()
{
multTime(3.14159);
multTime(NAN);
powTime(3.14159);
powTime(NAN);
}
编辑:
不幸的是,我对这个主题的了解非常有限,但我想glibc pow()
从不在32位系统上使用SSE,而是在sysdeps/i386/fpu/e_pow.S
中使用某些程序集。最近的glibc版本中有一个函数__ieee754_pow_sse2
,但它位于sysdeps/x86_64/fpu/multiarch/e_pow.c
中,因此可能仅适用于x64。但是,所有这些都可能与此无关,因为pow()
也是gcc built-in function。如需简单的解决方法,请参阅Z boson's answer。
答案 0 :(得分:8)
&#34;如果使用SSE而不是x87 FPU进行浮点数学计算,NaN不会对性能产生影响。&#34;
我不确定这会引用您引用的资源。在任何情况下,pow
都是C库函数。它不是作为指令实现的,即使在x87上也是如此。因此,这里有两个不同的问题 - SSE如何处理NaN
值,以及pow
函数实现如何处理NaN
值。
如果pow
函数实现对+/-Inf
或NaN
等特殊值使用不同的路径,则可能需要基数或指数的NaN
值,快速返回一个值。另一方面,实现可能不会将此作为单独的情况处理,并且仅依赖于浮点运算将中间结果传播为NaN
值。
从Sandy Bridge&#39;开始,减少或消除了与非正规相关的许多性能损失。但并非所有,因为作者描述了对mulps
的惩罚。因此,期望并非所有涉及NaNs
的算术运算都是“快速”是合理的。有些架构甚至可能会回复到微码以在不同的上下文中处理NaNs
。
答案 1 :(得分:3)
你的数学图书馆太旧了。要么找到另一个用NAN实现pow的数学库,要么实现这样的修复:
inline double pow_fix(double x, double y)
{
if(x!=x) return x;
if(y!=y) return y;
return pow(x,y);
}
使用g++ -O3 -msse2 -mfpmath=sse foo.cpp
进行编译。
答案 2 :(得分:2)
如果您想要平方或取平方根,请使用d*d
或sqrt(d)
。除非您的编译器根据常量第二个参数2和0.5对它们进行优化,否则pow(d,2)
和pow(d,0.5)
将会更慢并且可能更不准确;请注意,pow(d,0.5)
可能无法始终进行此类优化,因为如果d
为负零,则返回0.0,而sqrt(d)
则返回-0.0。
对于那些做时间的人,请确保你测试同样的事情。
答案 3 :(得分:2)
使用像pow()这样的复杂函数,NaN可以通过很多方式触发缓慢。可能是NaN上的操作很慢,或者可能是pow()实现检查它可以有效处理的各种特殊值,并且NaN值使所有这些测试都失败,导致更昂贵的路径被带走。您必须单步执行代码才能确定。
最近实施的pow()可能包括更有效地处理NaN的额外检查,但这总是一个权衡 - 让pow()处理&#39;正常&#39;这将是一种耻辱。为了加速NaN处理,情况更慢。
我的博客文章仅适用于个别说明,而不是复杂的功能,如pow()。