Std :: pow very different behavior for different indicators

I'm currently trying to optimize code where 50% of the time is spent on std::pow() . I know that the exponent will always be a positive integer, and the base will always be double in the interval (0, 1). For fun, I wrote a function:

 inline double int_pow(double base, int exponent) { double out = 1.0; for(int i = 0; i < exponent; i++) { out *= base; } return out; } 

I am compiling with:

 > g++ fast-pow.cpp -O3 --std=c++11 

I created 100 million doubles between (0, 1) and compared the timings (1) std::pow (2) of my homemade int_pow function from above and (3) direct multiplication. Here is a sketch of my temporary procedure (this is a very quick test):

 void time_me(int exp, size_t reps) { volatile double foo = 0.0; double base = 0.0; size_t i; for (i = 0; i < reps; ++i) { base = ((double) rand() / (RAND_MAX)) + 1; foo = pow(base, exp); // foo = int_pow(base, exp); // foo = base * base * base; } // check that the loop made it to the end std::cout << foo << " " << i << std::endl; } int main() { std::clock_t start; start = std::clock(); time_me(3, 1e8); std::cout << "Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << std::endl; return 0; } 

Below are the timings that I observed for different indicators:

  • 0: std::pow 0.71s, int_pow 0.77s
  • 2: std::pow 1.31s, int_pow 0.80s, direct mult 0.86s
  • 3: std::pow 6.9s (!!) , int_pow 0.84s, direct mult 0.76s
  • 5: Similar to 3:

My questions

So my questions are:

  • Why is the performance of std::pow deteriorating so much that it is greater than 2?
  • Is there a faster power function when basic or exponential types are known ahead of time?
  • Is there something completely obvious, I don’t notice? I am going to go through gut std::pow for cases with known integers and would not want to miss something completely trivial.

Thanks!!

+6
source share
2 answers

std::pow() is a general purpose function designed to accept any pair of floating point values. It performs costly calculations and should be considered a slow function. However, apparently, many nations abused it for squaring, so the implementation of pow() in the IBM Accurate Mathematical Library (which is used by glibc) was optimized for this particular case:

sysdeps / ieee754 / dbl-64 / e_pow.c :

 double __ieee754_pow (double x, double y) { ... ... if (y == 1.0) return x; if (y == 2.0) return x * x; if (y == -1.0) return 1.0 / x; if (y == 0) return 1.0; 

As you can see, the values ​​of the exponent 0, 1, and -1 are also processed specially, but those are at least mathematically significant special cases, while squaring is just a statistically significant case that otherwise does not deserve special processing). EDIT : The exponent values 0 , 1 , 2 and -1 are the only ones that allow std::pow(x,n) to be expressed using (much faster) arithmetic operations without loss of precision. See this answer for more details. Thus, the exponential value of 2 is not only a statistically significant case. End edit

If you need a quick alternative to std::pow() for non-negative integer exponent values ​​and don't care about a slight loss of precision, then

The boundary value of the indicator for choosing between the 1st and 2nd methods should be found using careful benchmarking.

+8
source
 switch (n) { case 0: return 1; case 1: return x; case 8: x*= x; case 4: x*= x; case 2: return x * x; case 6: x*= x; case 3: return x * x * x; case 5: y= x * x; return x * y * y; case 7: y= x * x * x; return x * y * y; ... }; 
+1
source

All Articles