pow(NAN) is very slow
pow(NAN) is very slow
对于NaN值,pow()
灾难性性能的原因是什么?据我所知,如果浮点运算是用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的乘法/除法现在快多了(实际上比实数快),但是平方和平方根仍然需要很长时间。
测试代码(使用gcc 4.1.2在32位OpenSuSE 10.2在VMWare下编译,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内置函数。要解决这个问题,请参见Z玻色子的答案。
"如果浮点运算是用SSE而不是x87 FPU完成的,nan应该不会对性能产生影响。"
我不确定这是否符合你引用的资源。在任何情况下,pow
都是一个C库函数。即使在x87上,它也不是作为指令实现的。所以这里有两个独立的问题- SSE如何处理NaN
值,以及pow
函数实现如何处理NaN
值。
如果pow
函数的实现对特殊值(如+/-Inf
或NaN
)使用不同的路径,您可能期望基数或指数的NaN
值能够快速返回值。另一方面,实现可能不会将此作为单独的情况处理,而只是依赖于浮点操作将中间结果传播为NaN
值。
从"Sandy Bridge"开始,许多与异常相关的性能惩罚被减少或消除。但并非全部,正如作者描述的mulps
的惩罚。因此,可以合理地预期,并非所有涉及NaNs
的算术运算都是"快速"的。有些体系结构甚至可以在不同的上下文中恢复为微码来处理NaNs
。
你的数学库太旧了。要么找到另一个更好地使用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
编译
如果要平方或取平方根,请使用d*d
或sqrt(d)
。pow(d,2)
和pow(d,0.5)
会更慢,而且可能不太准确,除非你的编译器基于第二个常数2和0.5对它们进行优化;请注意,这样的优化可能并不总是适用于pow(d,0.5)
,因为如果d
是负零,它返回0.0,而sqrt(d)
返回-0.0。
对于那些做计时的人,请确保你测试的是相同的东西
对于像pow()这样的复杂函数,NaN触发慢的方式有很多。可能是对NaN的操作很慢,也可能是pow()实现检查了它可以有效处理的各种特殊值,而NaN值未能通过所有这些测试,从而导致采用更昂贵的路径。
最近的pow()实现可能包括额外的检查以更有效地处理NaN,但这始终是一种权衡——为了加速NaN处理而让pow()更慢地处理"正常"情况将是一种耻辱。
我的博客文章只适用于单个指令,而不是像pow()这样的复杂函数
- cuda 9.2 curand_init extremely slow
- p0083中"very efficient factories for elements"的目的
- C++ OpenCV imdecode slow
- The mysql_query() is slow
- 使用 __builtin_expect() 或 Linux 内核时可能和不太可能时"very likely"多少
- QFileDialog Slow Network
- QT 5.2.0 getOpenFileName slow
- unordered_multimap::equal_range slow
- 有没有办法在调试中设置断点"at this very moment"?它是关于任何编程语言或IDE的
- ReadFile Rs232 Too Slow
- FileFlushBuffer() is so slow
- 如何让Very Sleepy分析器识别c++二进制文件中的函数名?
- SetPixel() is too slow
- GetPixel is WAY too slow
- pow(NAN) is very slow
- Very Sleepy分析器中花括号附近的时间测量值表示什么?
- count3's in cuda is very slow
- Is glGetShaderiv slow?
- Very Quick global .h Question(有了它,有些项目仍然没有定义)
- Erode is too slow - Opencv