perf(ecc): price the batch-affine chunk processor in the MultiExp cost model - #875
Open
4waan wants to merge 4 commits into
Open
perf(ecc): price the batch-affine chunk processor in the MultiExp cost model#8754waan wants to merge 4 commits into
4waan wants to merge 4 commits into
Conversation
MultiExp picks the pippenger window size c by minimising estimated
group operation count:
`cost(c) = (fr.Bits + 1) * (nbPoints + 2^c) / c`
The comparison was strict and the loop ascends over the implemented
window sizes, so whenever two window sizes score identically the
smaller one won. Setting cost(c) = cost(c+1) makes the (fr.Bits + 1)
factor cancel, and for a contiguous window set the model is exactly
indifferent at nbPoints = 2^c * (c - 1).
Windows c <= 9 use the extended Jacobian chunk processor, while
c >= 10 use the affine one to kill single field inversion over
the bunch of bucket additions.
Inversion costs roughly 84 muls spreading it across 80 to 640
bucket additions -> affine addition..cheaper than the Jacobian mixed adds
The cost model counts a bucket accumulation and a bucket reduction as
one group operation each and cannot see any of this
Exactly one tie per curve crosses the Jacobian/batch-affine boundary->
n = 4096 c 9 -> 10 bn254, bls12-377, bls12-381, bls24-315,
bls24-317, grumpkin, stark-curve,
secp256k1, secp256r1
n = 2816 c 8 -> 10 bw6-761
n = 7424 c 8 -> 12 bw6-633
4096 is ScalarsPerBlob, so the eip4844 commitment and proof paths sat
exactly on the wrong side of this tie.
Measured on my apple M2, BLS12-381 G1, min of 5 runs:
n=4095 tasks=1 76.73 ms -> 76.66 ms (control, c unchanged)
n=4096 tasks=1 76.65 ms -> 64.76 ms -15.5%
n=4097 tasks=1 64.74 ms -> 64.73 ms (control, c unchanged)
n=4096 tasks=16 13.12 ms -> 10.27 ms -21.7%
Extract bestC from MultiExp into a package-level bestCG1/bestCG2 so the window selection is reachable from a test, then assert it returns the largest cost-minimising window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only one tie per curve crosses the Jacobian/batch-affine boundary, not all.
4waan
commented
Aug 22, 2026
4waan
marked this pull request as ready for review
August 22, 2026 08:01
…t model Weight a bucket accumulation by the batch its inversion amortises over, use the same model for the split probe, and give costFunction a real CPU count.
Author
|
@gbotrel , a review would be great for me to push ahead my work! could you approve CI please? |
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.
Description
MultiExppippenger window counts bucket accumulation as whenc<=9it uses sequential extended jacobian whilec>=10accumulates with `processChunk * Batchffine* which ends a single field inversion over a whole batchBLS12-381 G1,
_innerMsmG1at fixedcso the split heuristic is out of the picture, on apple M2:The 9 -> 10 boundary belongs near n=1300, not 4096.
What this changes
Mean distance from the per-size optimum, timing
_innerMsmat fixedcover BLS12-381 and BN254 G1, nbPoints 1024..32768, c 8..13:w = 0.76Window boundaries, BLS12-381 G1:
Every one of those matches the measured optimum.
c=9is no longer selected: it never wins against bothc=8andc=10.Benchmarking
BLS12-381 G1
MultiExp, master vs this branch, M2 8-core, min of 5 interleaved rounds against a master worktree.Worst delta across 36 configurations is +5.4%, at a sub-millisecond size where the same n is -9.7% at
NbTasks=32. The n=8192 /NbTasks=8regression noted on the previous revision is gone (-5.6%).[notes by llm]
{4,5,8,10,16}) and inherit constants fitted on BLS12-381 and BN254. The fit is flat and the boundaries are independent offr.Bits, but this is an extrapolation and is not directly measured.NbTasks >= NumCPUsplitting cannot add parallelism, only work, and every split I could measure loses (from +5.6% at n=200000 to +21.3% at n=1540). This PR only makes the cost model self-consistent and stops it over-splitting; it does not redesign the heuristic. Happy to open that follow-up.Checklist:
internal/generator/ecc/generate.goand themultiexp*.go.tmpltemplates; theecc/*/multiexp*.goin this diff are the output ofgo generate ./internal/generator/...golangci-lintdoes not output errors locally (0 issues)