pow(NAN)非常慢

时间:2014-07-24 08:50:21

标签: c++ performance nan pow

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

4 个答案:

答案 0 :(得分:8)

&#34;如果使用SSE而不是x87 FPU进行浮点数学计算,NaN不会对性能产生影响。&#34;

我不确定这会引用您引用的资源。在任何情况下,pow都是C库函数。它不是作为指令实现的,即使在x87上也是如此。因此,这里有两个不同的问题 - SSE如何处理NaN值,以及pow函数实现如何处理NaN值。

如果pow函数实现对+/-InfNaN等特殊值使用不同的路径,则可能需要基数或指数的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*dsqrt(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()。