encode: write every BufMut through one pre-sized cursor - #467
Merged
Merged
Conversation
The provided encode methods on Message and ViewEncode now compute the exact size, then write any BufMut into that many contiguous bytes of its spare capacity through a shared bounds-checked cursor (PreSized). Each message's write_to is compiled once for all BufMut sinks instead of once per sink type, and encoding into a BytesMut with room no longer makes a call per tag and varint byte. A sink with less room than the message is filled through a scratch Vec and appended with one put_slice. Rope and types that implement EncodeSink without BufMut still receive every write individually. Generated lazy views and DynamicMessage are unchanged. A write_to that produces more bytes than compute_size declared now panics in release builds too, and in debug builds one that produces fewer panics in every encode entry point.
|
All contributors have signed the CLA ✍️ ✅ |
This was referenced Sep 23, 2026
iainmcgin
marked this pull request as ready for review
September 26, 2026 18:53
iainmcgin
enabled auto-merge
September 26, 2026 18:53
rpb-ant
approved these changes
Sep 26, 2026
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
A generated
write_tois instantiated once per sink type, so a program that encodes intoVec<u8>,BytesMut, and aRopecarries three copies of every message's write code. This change writes everyBufMutthrough one concrete cursor, so theBufMutsinks share one instance per message.On the whatsapp
waprotoschema (334 messages, 3,477 fields; fat LTO,text + data), a program that encodes into three sinks shrinks by 6.2% atopt-level = "z", 12.7% ats, and 23.0% at 3. A program that encodes into one sink grows 0.8% atzand 0.5% ats, and shrinks 16.0% at 3. On bare metal (c7i.metal-24xl, ±5% run-to-run, LTO), encoding into aBytesMuttakes 0.19–0.63 of the time it did and into a freshVec0.57–0.73,encode_to_vecandencode_to_bytesare within 0.97–1.07 of it, and encoding into a reusedVectakes 0.99–1.07, up to 7% longer.When the
BufMut's current chunk has room for the whole message,EncodeSink::__write_pre_sizedgiveswrite_toa bounds-checkedPreSizedcursor over that spare capacity, then advances the buffer by the bytes written. A shorter chunk (a freshVec::new()offers 64 bytes) is encoded into an exact-size scratchVecand appended with oneput_slice.Ropeand other non-BufMutsinks still receive every write individually, and lazy views andDynamicMessageencode as before.Behaviour changes:
BufMutsink, awrite_tothat writes more bytes thancompute_sizereported panics in every build. Before, it panicked only with debug assertions or grew the buffer. One that writes fewer bytes panics with debug assertions, now from every encode entry point.BufMutwrapper that overridesput_sliceno longer sees the message bytes throughput_*; they arrive throughadvance_mut, or through oneput_slicewhen the message is staged.Four
unsafeblocks. In__write_pre_sized, one views the chunk's firstlenbytes as[MaybeUninit<u8>]after checkingchunk.len() >= len, and one callsadvance_mut(written).PreSized::put_slicecallscopy_nonoverlappingafter a bounds check, andwrite_to_new_veccallsset_len(written). Every store is bounds-checked andwrittencounts only bytes stored, so no byte pastwrittenis claimed.The alternative to staging a short chunk is calling
reserveon the buffer.BufMuthas noreserve, so that would need a separateVec-only path.New hidden public items:
EncodeSink::__PRE_SIZED,EncodeSink::__write_pre_sized, andPreSized. The #437 changelog fragment loses its note that encoding into aBytesMutis slow, which this change fixes.