2.6 KiB
Optimization: rsqrt + Newton, divide killed
- Author: Linus
- Predecessor: variant_float_inlining_SoA_AVX2_polyexp.cpp
- File(s): variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp
- Status: approx 10 to 20 percent faster
What this changes
We replace all three _mm256_sqrt_ps calls with _mm256_rsqrt_ps, which gives us 1/sqrt(x) directly. We then recover the sqrt itself with one extra multiply (sqrt(x) = x * rsqrt(x)) and reuse the inverses to rewrite the force scaling so that the final vdivps disappears. No Newton-Raphson refinement: we accept the ~12-bit precision of plain vrsqrtps since the verify tolerance is 1cm.
Code
See variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp. Asm
has 3 vrsqrtps, 0 vsqrtps, 0 vdivps.
Benchmark
Conditions: N_ITER=1000, -O3 -march=native -flto, diff vs the polyexp predecessor.
x86
Machine: TBD
Frequency: TBD
| N | W(n) [Flops] | T(n) [Cycles] | Performance [Flops/Cycle] | Diff vs Predecessor |
|---|---|---|---|---|
| 50 | ||||
| 100 | ||||
| 200 | ||||
| 500 |
ARM
no avx2 here
Analysis
The win comes primarily from killing the vdivps, not from the sqrt latency
change. vdivps has 5-cycle throughput on port 0 (the divider port), and
each j-block had exactly one of them in the force-scaling step. Removing it
frees up port 0 for the three sqrts that also live there.
Replacing vsqrtps with vrsqrtps + multiply contributes a smaller piece:
it shortens each sqrt from ~12 cycles latency to ~8, and the muls land on
the FMA ports rather than port 0, so the divider port load drops further.
The accuracy cost is ~12 bits of mantissa precision, which is well within
the 1cm verify tolerance .
I also tried adding a Newton-Raphson refinement step to recover full sqrt precision. That variant ran slower than the pure-rsqrt version: the NR step adds 2 muls and one fnmadd on top of rsqrt, putting the recovered sqrt at ~20 cycles of latency, which is worse than vsqrtps's 12.
The speedup is consistent (~18-22% across all N), which suggests the divider port is on the j-block critical path at every problem size, not just at one particular working-set range.
Takeaway
probably not worth keeping. polyexp is faster on the perf machine at every size we measured.