You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#142 speeds up the scalar x86-64 GHASH (the PR body says scalar AES-GCM goes from 50 to 109 MiB/s on 16 KiB). Its Benchmarks run (job) shows no change:
Benchmark
Base
Head
Change
aes-128-gcm-encrypt/16384
4.24 µs
4.25 µs
0.3% slower
aes-128-gcm-stream/16384
4.30 µs
4.27 µs
0.6% faster
4.25 µs for 16 KiB is about 3.6 GiB/s, which is the AES-NI + PCLMULQDQ path from #137. The scalar path at 109 MiB/s would take about 140 µs.
Cause:AesGcm::new (src/aes_gcm.rs) picks the backend at run time, and GitHub's x86-64 runners have AES-NI, PCLMULQDQ and SSSE3. So every benchmark runs vg_ghash_pclmul, and the vg_ghash that #142 changes never runs. The comparison is correct (nothing it measured changed), but it measures the wrong code.
The same blind spot already covers SHA-256 and HMAC-SHA-256 on x86-64 (the SHA-NI dispatch in src/hashes/sha256.rs). It will cover every future SIMD or crypto-extension backend: the fallbacks those leave behind get no performance signal at all, and a regression in them would not fail the check either.
Two smaller problems make bulk-cipher results harder to read:
The summary shows times, not throughput. For a bulk cipher, "4.25 µs" means little without doing the arithmetic. The groups already declare Throughput::Bytes, so MiB/s is free to report.
Every iteration includes key setup (key expansion plus computing H for GCM). That's the right default ("one complete operation", bench/benches/primitives/main.rs), but at 64 and 1024 bytes setup dominates, and there is no measurement of steady-state per-byte cost.
Plan
The aim is a benchmark of every backend the runner can execute, without making the per-PR Benchmarks job noticeably slower. #142's x86-64 benchmark step took 70 s.
Benchmark each backend separately. The API already has __with_features(mask) for tests (AesGcm, AesGcmStream, the hash macro in src/hashes/mod.rs, Hmac). Add a backend to the benchmark id, e.g. aes-128-gcm-encrypt/verified-garbage@scalar/16384 and …@aesni/16384. Skip a backend whose features the runner lacks (cpu::available on the mask), and print that it was skipped rather than silently falling back. bench_compare.py only needs its id parsing extended.
Keep the extra backends cheap on PRs. Non-default backends run only the encrypt (or digest) group at 16 KiB, not every group at every size. That adds about 4 benchmarks (AES-GCM and SHA-256 scalar, plus HMAC later), roughly 4 × 0.7 s × 6 runs ≈ 17 s, and only on PRs whose changed modules select them (Benchmarks: run only the benchmarks of the modules a change touches #133's USES). The full cross-product (every group, size and backend) goes to step 5.
Report throughput. Read throughput from criterion's benchmark.json and add MiB/s columns for Base and Head. That costs no benchmark time.
Say which backend ran. Add a line to the summary listing the CPU features the runner detected (cpu::detected), so a reviewer can see why a number is what it is. This is what would have caught GHASH on x86-64: carry-less products with mul (BearSSL ctmul64) #142 at a glance.
Put the slow sweep on a schedule, not on PRs. A weekly (or workflow_dispatch) run of every benchmark on every backend, at every size plus one large size (e.g. 1 MiB) and a keyed-once variant that moves setup out of the loop. It compares with the previous week's commit through the existing base_commit input. This is where steady-state throughput and overall trends live, and it adds nothing to per-PR time.
Optional: like-for-like OpenSSL rows. For scalar rows, run OpenSSL's reference with OPENSSL_ia32cap masking AES-NI and PCLMULQDQ, so "Head vs OpenSSL" compares against OpenSSL's own portable code rather than its AES-NI code. That's one extra OpenSSL run per affected group, and it can be dropped if it isn't worth the time.
Backends that need features no hosted runner has (e.g. future AVX-512/VAES) can't be timed meaningfully under SDE. Those rows should show "not benchmarked (runner lacks X)" instead of disappearing.
Order: 1 to 4 in one PR (bench crate and ci/bench_compare.py only, no library or Lean changes). 5 follows in its own PR, and 6 only if wanted. Once the first PR lands, #142's Benchmarks run should show the scalar speedup.
Problem
#142 speeds up the scalar x86-64 GHASH (the PR body says scalar AES-GCM goes from 50 to 109 MiB/s on 16 KiB). Its Benchmarks run (job) shows no change:
aes-128-gcm-encrypt/16384aes-128-gcm-stream/163844.25 µs for 16 KiB is about 3.6 GiB/s, which is the AES-NI + PCLMULQDQ path from #137. The scalar path at 109 MiB/s would take about 140 µs.
Cause:
AesGcm::new(src/aes_gcm.rs) picks the backend at run time, and GitHub's x86-64 runners have AES-NI, PCLMULQDQ and SSSE3. So every benchmark runsvg_ghash_pclmul, and thevg_ghashthat #142 changes never runs. The comparison is correct (nothing it measured changed), but it measures the wrong code.The same blind spot already covers SHA-256 and HMAC-SHA-256 on x86-64 (the SHA-NI dispatch in
src/hashes/sha256.rs). It will cover every future SIMD or crypto-extension backend: the fallbacks those leave behind get no performance signal at all, and a regression in them would not fail the check either.Two smaller problems make bulk-cipher results harder to read:
Throughput::Bytes, so MiB/s is free to report.bench/benches/primitives/main.rs), but at 64 and 1024 bytes setup dominates, and there is no measurement of steady-state per-byte cost.Plan
The aim is a benchmark of every backend the runner can execute, without making the per-PR Benchmarks job noticeably slower. #142's x86-64 benchmark step took 70 s.
__with_features(mask)for tests (AesGcm,AesGcmStream, the hash macro insrc/hashes/mod.rs,Hmac). Add a backend to the benchmark id, e.g.aes-128-gcm-encrypt/verified-garbage@scalar/16384and…@aesni/16384. Skip a backend whose features the runner lacks (cpu::availableon the mask), and print that it was skipped rather than silently falling back.bench_compare.pyonly needs its id parsing extended.USES). The full cross-product (every group, size and backend) goes to step 5.throughputfrom criterion'sbenchmark.jsonand add MiB/s columns for Base and Head. That costs no benchmark time.cpu::detected), so a reviewer can see why a number is what it is. This is what would have caught GHASH on x86-64: carry-less products with mul (BearSSL ctmul64) #142 at a glance.workflow_dispatch) run of every benchmark on every backend, at every size plus one large size (e.g. 1 MiB) and a keyed-once variant that moves setup out of the loop. It compares with the previous week's commit through the existingbase_commitinput. This is where steady-state throughput and overall trends live, and it adds nothing to per-PR time.OPENSSL_ia32capmasking AES-NI and PCLMULQDQ, so "Head vs OpenSSL" compares against OpenSSL's own portable code rather than its AES-NI code. That's one extra OpenSSL run per affected group, and it can be dropped if it isn't worth the time.Backends that need features no hosted runner has (e.g. future AVX-512/VAES) can't be timed meaningfully under SDE. Those rows should show "not benchmarked (runner lacks X)" instead of disappearing.
Order: 1 to 4 in one PR (bench crate and
ci/bench_compare.pyonly, no library or Lean changes). 5 follows in its own PR, and 6 only if wanted. Once the first PR lands, #142's Benchmarks run should show the scalar speedup.🤖 Generated with Claude Code