Repository navigation
perf(pcs): amortize ARM EOR3 Basis map visits - #556
Merged
tcoratger merged 1 commit intoOct 10, 2026
Merged
Conversation
Benchmarks: base against this PR, on one runnerce33fe4 merged into its base, against the base 5468192: each benchmark run in turns on one runner, base and PR 5 times each. A size is shown when it changed; a time or a peak memory when it moved the same way in every round and its median by at least 1% (the change is the median ratio, with the rounds' range). Threads shows the named configuration; BLAKE2s
Prover stages
FibonacciProver stages
Flock
PCS
leanSPHINCS
Prover stages
leanXMSS
Prover stages
Every resultBLAKE2s
Prover stages
Fibonacci
Prover stages
Field and transpose kernels
Flock
PCS
leanSPHINCS
Prover stages
leanXMSS
Prover stages
|
kevaundray
marked this pull request as ready for review
October 8, 2026 12:02
tcoratger
approved these changes
Oct 8, 2026
tcoratger
left a comment
Collaborator
There was a problem hiding this comment.
We need to solve the conflicts and merge this one I think, very small changes and perf gains :)
Rebase the final production change from PR leanEthereum#556 onto riscv-exploration. Preserve the closed-form lane fold and current module layout; apply longer ARM fill windows and initialize scratch directly on the heap. Original commits by Kev Au. Squash the experiment scaffolding and its removal into the final production diff.
tcoratger
force-pushed
the
kw/basis-locality
branch
from
October 10, 2026 21:27
7049a2c to
ce33fe4
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.
Change
Use 1024-word initial Basis fill windows on the ARM EOR3 byte-table map backend (
aarch64plussha3), retaining 256-word windows on scalar and x86 byte-sliced backends. This gate matches the selected map implementation, not a workload or worker count. Smaller lane blocks still fill whole.Initialize first-pass scratch directly in a zeroed
Box, avoiding a large stack temporary. Its fields are integer-only arrays, including F192 and aligned WeightRow; zero bits are valid and provide the required padded-lane zeros.Boxhandles alignment, allocation failure and destruction. The explicitWeightFold::defaultpreserves zero initialization and is necessary because the longer window makes its outer array exceed the 32 entries supported by the derived array Default implementation.No arithmetic kernel, protocol, transcript, proof encoding, guest, retention/refill policy or dispatch nesting changes. The byte-table backend still retains its weight at every pool size; SIMD backends still retain on small pools and refill on larger ones. Diagnostic hooks and workflow changes are removed from the production diff.
Paired native evidence
Exact baseline:
f52bd991c8623894e7b03a2e50bede6c248ea1ad, including #549 and #550, excluding #552 and guest rewrites. Screening run 37764826302 compares 256, 512 and 1024 usinga16d22e615e767579c5c227946bdfbf1a33cab0e. Final confirmation 37768993635 compares the selected backend gate at1a808a3397c2d8e73eff97c17ab42f5d31bc0646; its production Rust sources are unchanged at clean head82795fc86ed57738246de42846182e68d52cf1ac.Both variants run on the same Neoverse-V2 or Xeon Gold 6548N machine with rustc 1.99.0 (
b940084d7), release thin LTO, one codegen unit and-C target-cpu=native. Each cell has five alternating whole-proof runs after the CLI warmup. Explicit pools are 1, 4 and 8; final captures confirm default pools of 16 on V2 and 32 on Xeon. Aggregation leaves use log inverse rate 2; node rates are named below. Comparisons are within each cohort, never pooled across machines. Tables show paired percentage change as median [minimum, maximum]; negative is faster. Artifacts retain the individual durations, counted heap, allocations, RSS, disassembly and proof bytes.Selected V2 schedule
The first three rows reuse the screened ARM code, which the final gate leaves unchanged; all 60 pairs improve. The remaining rows cover the missing rate and wider recursion shape, with 59 of 60 pairs improving. For context, the single-worker log-1 leaf median is 1944.437 to 1898.170 ms; log-4 is 5952.848 to 5921.506 ms. A separate single-worker trace shows Basis 345 to 298 ms, but that is one trace, not a five-sample span distribution or an additive explanation of the proof result.
Final Xeon residuals, keeping its 256-word schedule
This is not a claim of zero runtime variation: the higher 2-to-1 node at eight workers still has a +1.31% median with a wide signed range, and the log-4 leaf has small positive low-worker medians. These residuals are reported rather than hidden behind the unchanged scheduling choice.
Rejected universal 1024 schedule
Universal 1024 is rejected because of these Xeon aggregation losses. The 512 candidate gives weaker V2 medians, approximately 1.3% to 1.7% faster, and mixed Xeon results. A separate local scalar-backend safety screen with rustc 1.99.0,
-C target-cpu=x86-64, five alternating leanXMSS-1 log-4 proofs and a low-load process lock gives +3.48% [-1.49%, +6.79%] at one worker and +0.03% [-0.40%, +2.94%] at four. That is not native V2/Xeon evidence and is why the gate does not cover generic full-byte tables. Scalar keeps 256. Full retained-weight staging is not selected: the earlier native experiment regressed Xeon and needs more scheduling machinery than the bounded winner.Normal PR benchmark matrix
PR Bench 37772535485 passes all 46 jobs, comparing the PR merge against the same
f52bd991base with rustc 1.99.0 and native flags. All VM leaf and aggregation proof medians improve on V2, ranging from -1.53% to -3.44%; the corresponding Xeon medians range from -1.83% to +0.93%. The default V2 4-to-1 job contains positive outliers despite its negative medians. Standalone PCS opening is -1.84% on V2 but +1.67% on Xeon; unchanged PCS commit also varies. These results support the bounded ARM optimization, not a universal speedup or additive kernel attribution.All normal Bench latency residuals: paired median [minimum, maximum] percent
Names without a thread suffix use the default pool. Flock and PCS rows are standalone component benchmarks, not VM proof timings.
Memory and work bounds
ARM first-pass scratch plus its accumulator grows from 134,592 to 429,504 bytes per active worker, or 131.44 to 419.44 KiB, in the same two allocations. At 16 workers this is 2.05 to 6.55 MiB, bounded independently of input size. The retained output size is unchanged. The zeroed heap constructor avoids stack materialization; emitted V2 R4 grid-task frames remain 1,008 bytes. First-fold staging and accumulators still have bounded window-sized stack arrays. Scalar and x86 keep their original smaller windows.
For the 39-lane, 262,144-word opening, per-lane fills and retained-copy calls decrease from 39,936 to 9,984; grid tasks decrease from 3,072 to 768. Map arithmetic applications and grid products do not change. ARM retained publication is ordinary copying, so there is no x86 streaming fence to remove. Point high-equality tables shrink as more low coordinates are generated per window.
V2 allocation counts are identical in both cohorts. In screening, whole-proof counted heap peaks are identical at one worker and change by at most 0.13 MiB at larger pools; RSS median changes range from -0.55 to +9.82 MiB. In final rate/shape confirmation, counted heap median changes range from -7.40 to +0.06 MiB and RSS from -4.13 to +8.16 MiB. Heap peak is often dominated outside Basis and does not conceal the per-worker scratch increase or cached allocator pages.
Final Xeon counted heap peak median changes range from -0.14 to +0.095 MiB and RSS from -4.55 to +3.19 MiB; whole-proof allocation count differences range from -1 to +10. Its per-worker scratch size and two-allocation count remain unchanged.
Across the broader normal Bench matrix, median counted heap changes range from -0.12 to +0.37 MiB on V2 and -0.07 to +0.09 MiB on Xeon. Median RSS changes range from -40.96 to +16.09 MiB on V2 and -6.15 to +3.84 MiB on Xeon. Allocation count differences range from -26 to 0 on V2 and -15 to +70 on Xeon; these are observed whole-benchmark residuals, not additional scratch allocations.
Correctness and validation
All 960 saved proof files compare literally equal for the same workload/rate/node, across variants, worker counts, architectures and both cohorts. This covers log-1 and log-4 leaves, log-2 aggregation leaves, first and higher 2-to-1 nodes at log 1, and first and higher 4-to-1 nodes at log 2. Every measured proof is checked by the production verifier. The log-1 leaf remains 307,670 bytes with SHA-256
b413d253b65a70e53e843d83f176c94df401dd06126d4df089b0a7c0c73f8a63.Exact clean-head Rust CI 37769096834 passes all 13 jobs, including release workspace tests, MSRV, Rustfmt, Rustdoc, x86/ARM Clippy, AVX-512 Clippy, guest checks and dependency checks. AVX2+VPCLMULQDQ, AVX2+GFNI and NEON kernel and bus tests actually run and pass. The Miri job covers the thread pool, not the entire prover.
Local validation additionally includes Rust 1.97 native PCS tests and production proof verification, Rust 1.99 scalar PCS tests and a real log-4 proof, Rust 1.97 ARM SHA3 and non-SHA3 cross compilation, and the retained-weight equality test under AddressSanitizer on nightly-2026-10-03. Existing tests cover retained and virtual/refill paths, small blocks, aligned chunk boundaries, partial groups, truncated lanes and padding. Cross compilation is build evidence, not hardware timing. No cache-miss or hardware-counter claim is made, and no combination with other optimization branches is measured here.
The published PR additionally passes all 62 checks, including its 46 normal Bench jobs, Rust PR CI 37772535449, RISC-V ACT4, Python and Counts. No production instrumentation or benchmark workflow modifications remain.