Recently I was profiling a program in which the hotspot is definitely this
double d = somevalue();
double d2=d*d;
double c = 1.0/d2 // HOT SPOT
The value d2 is not used after because I only need value c. Some time ago I’ve read about the Carmack method of fast inverse square root, this is obviously not the case but I’m wondering if a similar algorithms can help me computing 1/x^2.
I need quite accurate precision, I’ve checked that my program doesn’t give correct results with gcc -ffast-math option. (g++-4.5)
The tricks for doing fast square roots and the like get their performance by sacrificing precision. (Well, most of them.)
Are you sure you need
doubleprecision? You can sacrifice precision easily enough:The
1.0fis absolutely mandatory in this case, if you use1.0instead you will getdoubleprecision.Have you tried enabling “sloppy” math on your compiler? On GCC you can use
-ffast-math, there are similar options for other compilers. The sloppy math may be more than good enough for your application. (Edit: I did not see any difference in the resulting assembly.)If you are using GCC, have you considered using
-mrecip? There is a “reciprocal estimate” function which only has about 12 bits of precision, but it is much faster. You can use the Newton-Raphson method to increase the precision of the result. The-mrecipoption will cause the compiler to automatically generate the reciprocal estimate and Newton-Raphson steps for you, although you can always write the assembly yourself if you want to fine tune the performance-precision trade-off. (Newton-Raphson converges very quickly.) (Edit: I was unable to get GCC to generate RCPSS. See below.)I found a blog post (source) discussing the exact problem you are going through, and the author’s conclusion is that the techniques like the Carmack method are not competitive with the RCPSS instruction (which the
-mrecipflag on GCC uses).The reason why division can be so slow is because processors generally only have one division unit and it’s often not pipelined. So, you can have a few multiplications in the pipe all executing simultaneously, but no division can be issued until the previous division finishes.
Tricks that don’t work
Carmack’s method: It is obsolete on modern processors, which have reciprocal estimation opcodes. For reciprocals, the best version I’ve seen only gives one bit of precision — nothing compared to the 12 bits of
RCPSS. I think it is a coincidence that the trick works so well for reciprocal square roots; a coincidence that is unlikely to be repeated.Relabeling variables. As far as the compiler is concerned, there is very little difference between
1.0/(x*x)anddouble x2 = x*x; 1.0/x2. I would be surprised if you found a compiler that generates different code for the two versions with optimizations turned on even to the lowest level.Using
pow. Thepowlibrary function is a total monster. With GCC’s-ffast-mathturned off, the library call is fairly expensive. With GCC’s-ffast-mathturned on, you get the exact same assembly code forpow(x, -2)as you do for1.0/(x*x), so there is no benefit.Update
Here is an example of a Newton-Raphson approximation for the inverse square of a double-precision floating-point value.
Unfortunately, with
RECIP_ITER=1benchmarks on my computer put it slightly slower (~5%) than the simple version1.0/(x*x). It’s faster (2x as fast) with zero iterations, but then you only get 12 bits of precision. I don’t know if 12 bits is enough for you.I think one of the problems here is that this is too small of a micro-optimization; at this scale the compiler writers are on nearly equal footing with the assembly hackers. Maybe if we had the bigger picture we could see a way to make it faster.
For example, you said that
-ffast-mathcaused an undesirable loss of precision; this may indicate a numerical stability problem in the algorithm you are using. With the right choice of algorithm, many problems can be solved withfloatinstead ofdouble. (Of course, you may just need more than 24 bits. I don’t know.)I suspect the
RCPSSmethod shines if you want to compute several of these in parallel.