Repository navigation
Conversation
…ing) Pow (Exp of a scaled Log) is latency bound: Horner's scheme makes the 9 term log polynomial and the 6 term exp polynomial long dependency chains. In RELAXED mode evaluate them with Estrin's scheme instead (two more multiplies, about half the latency). Results round differently in the last bit: over the test input ranges the mean ulp error is unchanged, and the max error is 1.31 ulp for Exp (was 1.28) and 1.03 ulp for Log (was 0.83). Non-relaxed builds keep Horner's scheme and their exact results.
This was referenced Oct 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
FS::PowisExp( pow * Log( value ) ), andExpandLog(ToolSet/Generic/Functions.h) evaluate their polynomials with Horner's scheme. So the 9 coefficientLogpolynomial and the 6 coefficientExppolynomial are each one long chain of dependent multiply-adds, andPowruns one after the other. Code that callsPowa lot, like FastNoise2's Minkowski distance and PowFloat node, mostly waits on that chain.Change
In RELAXED mode only,
ExpandLoguse Estrin's scheme. Pairs of coefficients are combined with independentFMulAdds, and the pairs are then joined with x², x⁴ (and x⁸ forLog).Logalso folds its -x²/2 term into the lowest pair. This costs one extra multiply in each function and roughly halves the dependency chain.Non-relaxed builds keep Horner's scheme, so their results don't change.
This one is optional. RELAXED results round differently in the last bit, and some FastNoise2 cases that never call
Powget slower on GCC (see Costs).Accuracy
I compiled master's and this branch's functions into one program, with the flags FastSIMD's RELAXED test libraries use. Then I compared both with
std::exp/std::log/std::powin double precision over the test ranges:Exp: every float in [-87.3, 88.37].Log: every float in [1e-20, 1e20].Pow: 2^26 random pairs, with the base in [1e-4, 1e4] and the exponent in [-8, 8].ExpLogPowThese are the same with clang-cl 23, MSVC 19.51 and GCC 16 on SSE2, SSE4.1, AVX2 and AVX512. Homebrew clang 23 on the Apple M1 (NEON, AARCH64) matches the FMA figures. A changed
Powresult can move by up to 128 ulp, becausePowscales a 1 ulp change inLogbyb * ln( a ).FastNoise2 builds with fast-math, which reorders these expressions differently per compiler:
Lognow lands at 1.95-1.96 ulp max and 0.40 mean on every x86 compiler, which is where clang-cl already was. That is worse for MSVC/fp:fast(was 0.83 max, 0.25 mean) and better for GCC 16-ffast-math(was 2.53 max, 0.46 mean).Effect
I built FastNoise2 master (8c2d080) against this branch and against FastSIMD master, with the same compiler, and timed both on a Ryzen 9 7950X. Both builds are loaded in one process and timed interleaved on one pinned core. Each figure is the median of 3 processes with alternating order.
The Pow cases are the four Minkowski distance nodes (DistanceToPoint and CellularValue/Distance/Lookup, with a fixed and a node-driven P where available) plus PowFloat, in 2D, 3D and 4D. The controls are the Euclidean CellularValue/Distance/Lookup cases, which compile into the same functions but never call
Pow, plus PowInt. I left out the SSE2 4D PowFloat/PowInt cases, which move with code placement. Numbers are new/old time, geomean per feature set:On the Apple M1 with Homebrew clang 23 (unpinned, so noisier), NEON is 0.865 and AARCH64 0.866, with controls at 0.978 and 0.995.
Costs
Powshares the generator loop with the Euclidean path, and FastNoise2's AVX2 code works on pairs of 8-wide registers. The cellular generators' stack frames grow by 160-256 bytes, and their stack accesses by 14-22%.Exponly gains at most 4% but slows these cases just as much. So any biggerPowdoes this here, and the fix belongs in FastNoise2, by keeping the Minkowski path out of that loop.CalcDistanceout of line, with all the distance functions in one switch.Testing
f32 exp,f32 log,f32 powandf32 pow zero base, whose RELAXED tiers must stay within 8/8/256 ulp of the non-relaxed SCALAR result. With--statsthe largest RELAXED SIMD differences are now 1, 1 and 127 ulp. On master they are 0, because RELAXED ran the same Horner code. The non-relaxedf32 exp/log/pow full rangetests still match SCALAR bit for bit.CalcDistanceagain. Most of those are below 3e-6, and four are 1e-4 to 4.2e-4 (F3F4Div is the largest).Not included
The work this came from also replaced the two-part (Cody-Waite) ln 2 constant with a single
0.693147180559945309fin RELAXED mode. MSVC keeps the two parts even with/fp:fast, so that gave MSVC another 3% on the Pow cases. But without fast-math it breaksf32 expRELAXED: 27 ulp in the test, and up to 68 ulp over the range. For |x| above 64 the single productfx * ln2rounds by up to 3.8e-6, and the range reduction turns that into relative error. So I left it out.clang-cl's
/fp:fastbuilds of master already merge the two parts, and theirExphas that 68 ulp error on SSE2/SSE4.1 (4.6 ulp with FMA). That may be worth a separate look.Other PRs
Functions.h, in a different function.