Faster modular exponentiation of integers - #2807
Open
fredrik-johansson wants to merge 5 commits into
Open
Conversation
fredrik-johansson
force-pushed
the
fft26
branch
from
August 30, 2026 19:46
3ad88d1 to
c342c0b
Compare
fredrik-johansson
force-pushed
the
fft26
branch
from
August 30, 2026 19:52
c342c0b to
22deebd
Compare
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.
Developed using Claude Fable 5.
Adds
flint_mpn_powmandflint_mpn_powm_preinvnand uses these when applicable infmpz_powm,fmpz_powm_ui,fmpz_mod_pow_fmpz,fmpz_mod_pow_ui, and addsmpn_mod_pow_*.For large moduli and exponents this uses Montgomery reduction with several FFT tricks; a detailed writeup by Claude is attached below.
The speedup is ~1.3x asymptotically and up to 1.8x in a narrow band (moduli around 65K bits).
Speedup for
fmpz_powmfor a modulus with pb bits and exponent with eb bits:Speedup
fmpz_powmvsmpz_powm:The algorithms behind
flint_mpn_powm(AI-written)This document describes the algorithms implemented in
src/mpn_extras/powm.cfor computingwith
b,e,mgiven as mpn limb vectors (limb baseB = 2^64,modulus of
mnlimbs, exponent ofenlimbs). It states the preciseformulas used at each size tier and explains the underlying techniques.
A final section lists optimizations that remain on the table.
Throughout, "one mul" means one full
mn x mn-limb product at therelevant size; costs of partial products are quoted as fractions of it.
1. Overall structure and size tiers
Exponentiation is performed by a sliding-window square-and-multiply
ladder (section 2) whose inner operation is a modular multiplication
or squaring. Everything else in the file is about making that inner
operation cheap and about amortizing per-call setup. The tiers, keyed
by the modulus size
mnand the exponent bit length:mulmod_preinvnladder, minimal setup (§4)mn < 110(large exponents)mpz_powm(GMP'sredc_1/redc_2)110 <= mn < 120mulmod_preinvnladder (§4)120 <= mn < 480mn >= 480Inside the large tier, the
q*m mod (B^rlen - 1)product at the heartof the folded REDC is itself computed by one of three engines chosen at
setup time (§5.3): a cyclic FFT plan, the recursive
"chain" (§7), or
mpn_mulmod_bnm1.Even moduli are handled by the 2-adic splitting
m = 2^t * m_odd:the odd part runs through the machinery above, the power modulo
2^tis computed by a dedicated truncated ladder (
powm_2exp, allarithmetic modulo a power of two is plain low products), and the two
residues are recombined by CRT. The rest of this document assumes
modd where Montgomery arithmetic is involved.
At the
fmpzlevel, two fast paths run before any of this: an exactpower when
e <= (bits(m) - 1)/bits(b)guarantees|b|^e < m, and ampz_powm_uicall for short bases with exponents under 25 bits belowthe FFT tier, where per-call setup cannot be amortized.
2. Sliding-window exponentiation
For an exponent of
lbits and window parameterk, the ladderprecomputes the table of odd powers
which costs one squaring plus
2^(k-1) - 1multiplications. Theexponent is then consumed from the top: at each step the scanner
(
next_window) finds the longest window of at mostkbits that endsin a set bit, so every table multiplication is by an odd power, and
runs of zeros between windows cost only squarings. For a random
exponent this gives
kgrows withl(as in_gr_pow_mpn_sliding) but is capped so thatthe table of
2^(k-1)full-size entries stays modest. When the basefits in one or two limbs (
small_base), table multiplications arereplaced by
mul_scalar_mod: a full product by the 1–2-limb scalarfollowed by one division — asymptotically negligible next to the
squarings — with base 2 specialized further to a shift and a
conditional subtract.
The measured cost split at
mn = 1030reflects this design: thesquarings and the reductions dominate, the table multiplications are
a few percent.
3. Squaring
At every tier the squarings go through
flint_mpn_sqr. In the FFTrange this detects the equal operands and computes only one forward
transform, one pointwise pass, and one inverse transform, so that
(measured at 1030 limbs: 78 vs 105 microseconds). Since half or more
of the ladder's work is squarings, this factor is a large share of the
overall advantage over a generic-multiplication ladder.
4. The Barrett basecase: precomputed inverse of the modulus
4.1 The precomputed inverse
flint_mpn_preinvncomputes, for the modulus shifted into normalizedposition (
norm = clz(m[mn-1]),d = m << norm),an
n-limb approximation of the scaled reciprocal. The poweringladder keeps residues shifted left by
normso that every reductionsees a normalized divisor.
4.2 Barrett reduction (
flint_mpn_mulmod_preinvn)Given a
2n-limb productX < d * B^n, the quotient estimate iswhich satisfies
q <= floor(X/d) <= q + kfor a small constantk.The remainder candidate
then lies in
[0, k*d)and at mostkconditional subtractions ofdproduce the canonical remainder. One reduction therefore costs onemulhighplus onemullow, about one full multiplication, on top ofthe product being reduced.
4.3 The wraparound variant (
mulmod_preinvn_fold, 120–2000 limbs)The
mullow(q, d)above only ever feeds a subtraction whose result isknown to be tiny. It can therefore be replaced by a product in the
ring
Z / (B^g - 1)for a fold lengthgslightly larger than thebound on
r0: with the same quotient estimate,computed by
mpn_mulmod_bnm1recoversr0exactly because0 <= r0 < B^g - 1. Reducing an argument moduloB^g - 1is justfolding: split it into
g-limb chunks and add them with wraparoundcarry, since
B^g ≡ 1. The wraparound product costs roughly half ofa full product at these sizes, which is where the 120-limb tier gets
its advantage.
5. The large tier: Montgomery reduction with a folded quotient ledger
5.1 Montgomery representation and REDC
For odd
m, fixR = B^mn. Residues are kept in Montgomery formx~ = x*R mod m; the product of two Montgomery forms followed byyields the Montgomery form of the product, and the final result is
recovered by one more REDC. The inverse used is
computed by
_flint_mpn_binvertvia Hensel liftingv <- v*(2 - m*v) mod B^(2^i), doubling the precision each step.The classical REDC formula is
with
t < 2mwhenX < m*B^mn, so one conditional subtractioncanonicalizes. Note the signs: because
q*m ≡ X (mod B^mn)— thisimplementation uses
q = X_lo * minvand addsq*m, making the lowhalf
X_lo + q*m ≡ 2*X_lo... in fact the cancellation is arranged asfollows, which is exactly what the folded version exploits.
5.2 The quotient step, optionally in the transform domain
The
mullowcomputingqis the one dense product of the reductionthat is not shared with anything else. Above
FLINT_MPN_POWM_REDC_QSTEP_FFT_THRESHOLD = 480limbs it is computedin the transform domain against a cached transform of
minv(
Fminv, built once at setup):replacing an
mn x mnmullow by one forward transform, one pointwisepass, one inverse transform and one export — measured at 1030 limbs
this takes the whole reduction from 157 to 128 microseconds per call.
The threshold was tuned by same-process A/B on the merged pipeline;
the crossover sits between 420 and 500 limbs.
5.3 The folded ledger: recovering
tfrom a wraparound residueWriting
X = X_hi * B^mn + X_lo, the quantityX_lo + q*misdivisible by
B^mnby construction; call the quotient wordis the REDC output before canonicalization (the code short-circuits
X_lo = 0, whereq = 0andc = 0, tot = X_hidirectly). Theentire dense work is thus computing
q*m— and since onlycisneeded,
q*mnever has to be produced exactly: it suffices to know itmodulo
B^rlen - 1for anyrlen > mn, because division byB^mnin that ring is a rotation. Concretely, with
k = rlen - mn:Since
0 <= H <= m - 1 < B^rlen - 1, the only ambiguity of thewraparound representative —
H = 0versus the all-ones vector — isresolved by inspecting the top limbs, and
t' = X_hi + H + 1followsby one addition. The reduction has become: one quotient product (§5.2),
one multiplication modulo
B^rlen - 1, and linear work.The product
S = q*m mod (B^rlen - 1)is served by one of threeengines chosen at setup:
rlen = nncchosen byfft_small_plan_init_mpn_cyclicas thecheapest admissible wraparound length at or slightly above
mn,multiplied pointwise against a cached transform of
m(Fm).A cyclic convolution is multiplication modulo
B^N - 1: indexingchunks by powers of
B^bits, the convolution wraps indexNbackto
0. Onlyqis transformed per call. Cost ~ 0.55 mul.B^rn - 1,rnthe smallest power of two>= max(mn, 128), withcached negacyclic transforms of the modulus residues at each level.
mpn_mulmod_bnm1: GMP-style wraparound recursion, usedin the mid band where FFT plans do not yet pay.
Methods 0 and 1 are compared at setup by the calibrated cost model
27 * rn_chain <= 33 * nnc(chain wins when its power-of-two paddingis mild): the constants were validated on two microarchitectures to
within a few percent of measured ratios.
6. The two-prime FFT layer underneath
The transforms used above come from FLINT's
fft_smallmachinery.Its relevant properties for
powm:Chunked representation. An integer is split into slots of
bitsbits (
bitsaround 40–50 chosen by the plan); multiplication becomespolynomial multiplication of the slot sequences followed by carry
propagation.
Two-prime CRT (Garner). Slot products can exceed one word, so the
convolution is computed modulo two ~50-bit primes
p1, p2indouble-precision FFTs. Each slot is recovered by Garner's mixed-radix
formula:
evaluated 8 lanes at a time in floating point, followed by a scalar
sweep that shifts each
zinto its bit positionj*bitsof theoutput and accumulates with carries. The reconstruction is
destructive on the transform lanes (output conversion is destructive
by default throughout
fft_small; non-destructive conversions copyfirst), and runs as one full vectorized sweep followed by one scalar
sweep — the separated shape that store-forwarding rewards.
Truncated, low, high and window products. The transforms support
bit-granular truncation: only
itrslots are transformed and only anoutput window
[zl, zh)of limbs is exported, with slot boundsguaranteeing exactness of the window.
mulhighandmulloware thewindow specializations used by the Barrett and Montgomery quotient
steps. For signed accumulations the exports use centered
representatives in
(-P/2, P/2]with the sign resolved duringrecomposition.
Cyclic and negacyclic products. A length-
Ncyclic convolutioncomputes multiplication modulo
B^(N*bits/64) - 1with no zeropadding (used by the fold engine); a negacyclic convolution — the same
transform with a weight
w^japplied per slot,w^(2N) = 1,w^N = -1— computes multiplication moduloB^h + 1(used at everychain level). Both avoid the 2x padding of a plain product, which is
the entire point of the fold: the ladder's reductions run in rings
where the transform length matches the modulus size instead of
doubling it.
7. The chain: recursive CRT over
B^s - 1 = (B^h - 1)(B^h + 1)powm_chain_initfixesrn, the smallest power of two>= max(mn, 128), and builds the towers = rn, rn/2, ..., 64.At each level, multiplication modulo
B^s - 1splits by CRT(
gcd(B^h - 1, B^h + 1) = 1for evenB, usingh = s/2):The negacyclic products use
sd_fft_mpn_mulmod_2expp1with themodulus-residue transform
Fm[lev]precomputed once per level atsetup when
h >= 128(CHAIN_NEG_H), and a basecasemulmod_2expp1below that. Residue extraction is linear: moduloB^h - 1fold-and-add, moduloB^h + 1alternate-and-subtract.Recombination uses the explicit CRT for this coprime pair, in the
same form as
flint_mpn_mulmod_bnm1: withu ≡ y (mod B^h - 1)andv ≡ y (mod B^h + 1),where
s*B^h - s = s*(B^h - 1)vanishes moduloB^h - 1(preservingu) and equals-2s ≡ v - umoduloB^h + 1(correcting tov);the division by 2 is a shift after a parity fix using
B^h + 1 ≡ 0. The recursion bottoms out at 64 limbs with a plainproduct against the cached
m mod (B^64 - 1).The chain's appeal is that all of its per-level modulus transforms
are cached — the per-reduction work is one forward transform, one
pointwise pass and one inverse transform per level on the
qsideonly, at geometrically decreasing sizes. Its weakness is the
power-of-two
rn: atmnjust above a power of two the padding isnearly 2x, which is when the cyclic plan (whose admissible lengths are
much denser) wins — hence the 27:33 selector.
8. Where the constant factors come from: an accounting
Per exponent bit at
mn = 1030limbs (measured):against roughly 3 mul-equivalents per bit for a classical
Barrett ladder (1 squaring + ~2 for the reduction), matching the
observed ~1.3x asymptotic speedup over the previous implementation
and ~3x over
mpz_powmat large sizes.9. Remaining optimizations
Ordered roughly by expected value per unit of implementation effort.
Cache the transforms of the sliding-window table. Table
multiplications currently run as full products; transforming each
T[j]once at table-build time and multiplying pointwise againstthe (already transformed) accumulator would remove one forward
transform per table multiplication, ~30% of its cost. At large
exponents table muls are only a few percent of the total, but at
moderate exponent lengths (a few hundred bits) the table build plus
its multiplications are a visibly larger share, and the same cached
transforms would accelerate the build itself
(
T[j+1] = T[j] * T[0]^2reuses the transform ofT[0]^2).Cache the wraparound transforms in the 120–2000 band. The
mulmod_preinvn_foldbasecase callsmpn_mulmod_bnm1afresh perreduction, re-transforming the modulus every time; likewise
dinvis re-consumed by a plain
mulhighper reduction. A persistentB^g - 1context holding the transforms ofd(and, where thequotient product is large enough to transform, of
dinv) acrossthe whole ladder would mirror what the large tier already does with
Fm/Fminv. This band covers four octaves of sizes and currentlypays a per-reduction setup the large tier has eliminated.
Fuse the squaring with the reduction in the transform domain.
The fold path today exports the squaring
X = acc^2to limbs, thenimmediately transforms
X_lofor the quotient step andqfor theledger. The squaring's own transforms are thrown away at export.
A window-export design could keep
acctransformed across thesquare-reduce pair: export only
X_lo(a truncated window) for thequotient, and feed the ledger from the retained transform where
lengths allow. This is precisely the chain path's philosophy
extended into the fold; even a partial fusion (sharing the
fft(X_lo)between the quotient step and the-X_locorrection)removes one transform per bit.
Bit-granular chain lengths. The chain's 2x padding cliff at
mnslightly above a power of two comes fromrnbeing a power oftwo. The negacyclic machinery supports weighted lengths at finer
granularity (as the cyclic plans already exploit); a chain over
B^rn - 1withrnfrom a denser admissible set would move itscrossover into territory the cyclic plan currently owns, and make
the 27:33 selector nearly moot.
Signed-window (NAF) recoding. With centered signed exports
already supported by the FFT layer, a signed sliding window would
shrink the table by half for the same window width or lengthen the
effective window for the same table, trading table muls for
essentially free negations mod m.
A negacyclic fold. The ledger ring
B^rlen - 1could equallybe
B^rlen + 1(the rotation trick works with a sign), and thenegacyclic transform of the same length covers twice the integer
size; where admissible cyclic lengths near
mnare sparse, thenegacyclic ring of half the transform length may be cheaper.
Base-2 at large sizes. With a base-2 exponent the table entries
are powers
2^(2j+1)and every table multiplication is a shift;the current scalar special covers this, but the squaring chain
itself could exploit the sparsity of the initial segments (GMP does
at some sizes; the timing grid shows a remaining 6x gap at
pb = 32768, eb = 16for base 2, above the fmpz-level fallback'ssize cutoff).
Per-architecture constants. The tier thresholds and the 27:33
selector are calibrated on two x86-64 microarchitectures and agree
between them; they live in
powm.cpending aflint-mparam.hmigration if a future architecture (or the zen chain-selector
observation at sizes moderately below a power of two, where the
selector oversells the chain by up to ~4%) demands per-arch values.