Repository navigation
Conversation
alex
force-pushed
the
gcm-encrypt-copy-fast-path
branch
2 times, most recently
from
October 8, 2026 18:07
0cb14ce to
56f7dfa
Compare
|
This seems like the wrong fix, why are we not speeding up the encrypt op? |
reaperhulk
added this pull request to stack #1368
October 8, 2026 21:09
alex
force-pushed
the
gcm-encrypt-copy-fast-path
branch
from
October 8, 2026 22:06
56f7dfa to
51f365b
Compare
`AesGcm::encrypt` now copies the pieces into its output and encrypts them there in place (one call of `vg_aes_gcm_seal`, through a new internal `seal` that `encrypt_in_place` shares) when the text is shorter than its backend's `copy_below`: the call of `vg_aes_gcm_seal_gather` (its descriptors, its frame and work slots, its call of `seal` or of the streaming functions) costs more than a copy for short texts. With out-of-place interleaved loops (VAES + VPCLMULQDQ + AVX-512), the gathering pays for its call from about 3 KiB on when every piece but the last ends on a block boundary, and from about 16 KiB on when one does not (the streaming functions buffer the partial block, about 100 ns). Without them, the gathering copies the text too, so copying here costs less at any length; 64 KiB, above the largest TLS record, keeps it in use for longer texts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FoYWDDEDFq1DFev5X7n2aq
alex
force-pushed
the
gcm-encrypt-copy-fast-path
branch
from
October 8, 2026 22:18
51f365b to
637c8b4
Compare
Member
Author
|
Are you going to address my feedback? |
Member
Author
|
You're right, and I'm sorry I didn't answer sooner: your comments never reached my session, and I should have checked the thread. This PR adds dispatch in Rust around a slow function rather than making the function fast, so I'm closing it. What replaces it:
Each PR will include its measurements. Generated by Claude Code |
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.
Stacked on #1350 (which is stacked on #1331). This is a Rust-only change.
This comes from the rustls provider analysis in #991, the second step on #1228. The provider seals TLS records with
AesGcm::encryptfrom two pieces (the payload and the content-type byte). The goal is thatencryptis never slower than copying into the output and callingencrypt_in_place.What changes
AesGcm::encryptnow copies the pieces intooutand encrypts there in place when the text is shorter than its backend's threshold (Backend::copy_below). The copy uses memcpy, and the in-place encryption goes through a new internalsealthatencrypt_in_placenow shares, so the lengths aren't checked twice. Callingvg_aes_gcm_seal_gatherhas a fixed cost (the descriptors, its argument frame and work slots, then its call ofsealor of the streaming functions) that exceeds a memcpy for short texts.Thresholds, measured on a VAES/AVX-512 Xeon:
(3072, 16384). The gather pays for itself from about 3 KiB when every piece but the last ends on a block boundary. When one doesn't, the streaming functions buffer the partial block, which costs about 100 ns more, and the gather only catches up from about 16 KiB. TLS payloads are arbitrary lengths, so the unaligned case is common.(65536, 65536). Their gather copies the text too (x86-64 AES-GCM: seal_gather copies a short text and seals it in place #1350), so copying here costs less at any length. The 64 KiB cap is above the largest TLS record. It keepsvg_aes_gcm_seal_gatherin use, and tested, for longer texts, where the difference is negligible.The
matchis exhaustive over the x86-64 backends, likeBackend::prepared. Other architectures still always gather.Measurements
The setup is
encryptof two pieces (n bytes, then 1 byte), 13-byte AAD, warmed key, minimum of 33 interleaved samples, in ns. "Before" is #1350 and "after" is this PR."copy+ip" copies the pieces into the output and calls
encrypt_in_place, in a non-inlined function taking slices, as a caller like rustls would. Inlined, with the nonce and AAD lengths known at compile time, it measures 10–15 ns faster at small sizes; real callers don't have those lengths at compile time.VAES + VPCLMULQDQ + AVX-512
AES-NI + PCLMULQDQ + AVX (
VG_CPU_FEATURES=aes,pclmulqdq,ssse3,avx)I measured these before splitting out the internal
seal. After the split, AVX-512 at 0–511 bytes measures 0.92–1.03 of copy+ip. The other backends (VAES + VPCLMULQDQ on AVX2, AES-NI + PCLMULQDQ) measure 0.97–1.01 of copy+ip at every size.So
encryptnow matches copy+ip on every x86-64 backend. On AVX-512 it is about 8–9% faster for block-aligned payloads from 3 KiB. Being faster on the other backends needs out-of-place interleaved loops for them. Being faster on AVX-512 for unaligned payloads needs the streaming path to handle a block split between pieces without buffering it. Both are follow-ups.Interaction with #1331's two-slice path
#1331 now also has
seal_two_slices_prepared. It covers a prepared AVX-512 key with a 96-bit nonce, at most 16 bytes of AAD, and a piece of at least 256 bytes followed by at most one byte, which is exactly the rustls record shape.encrypttries it first and falls back to the copy above. I measured both orders on the same Xeon, with the same two-piece setup (minimum of 45 samples, in ns):The two-slice path first is 18–42% faster everywhere except where
n % 16 == 15. There the byte completes the last block, and #1331's path takes its separate tail call. That case belongs to #1331's code, so it is not special-cased here. For texts the two-slice path doesn't take (short texts, other shapes, other backends), the tables above stand.Tests
copy_belowcovers every backend the CPU has, on each side of both thresholds: one piece, a 16-byte piece then the rest, and a 1-byte piece then the rest. Each case is compared against the baseline's in-place encryption. The test is in theaes_gcmCPU group by module path, so every CI CPU runs it. #1331'sprepared_gather_tailsis kept alongside it.Validation
cargo testwith Wycheproof on the default backend, withVG_CPU_FEATURES=aes,pclmulqdq,ssse3,avx, and withVG_CPU_FEATURES=none;cargo fmt --check;cargo clippy --all-targets -- -D warnings;ci/check_variants.py;ci/check_arch_gates.py;ci/cpu_tests.py lint. Rerun after restacking on #1331's two-slice commit.🤖 Generated with Claude Code
https://claude.ai/code/session_01FoYWDDEDFq1DFev5X7n2aq