Repository navigation
Encode a string into the frame itself instead of into a scratch buffer - #17
Merged
Merged
Conversation
… buffer
The last per message allocation. A string field was encoded into a List<byte>
and the buffer was then copied into the frame, and for a sequence protocol the
pre pass that assigns the length fields encoded every string a second time only
to throw the result away.
The encoder now writes into the span the frame itself occupies, and the pre pass
only measures. One case still needs a buffer of its own: a value longer than its
fixed length field has to be cut at the byte level, and an encoder cannot write
into a span that is too small for its output. That one is rented from the pool
and only values that overflow their field ever reach it.
A/B of the same tree with and without the change, back to back:
Write string 114.74 ns -> 44.08 ns -61.6% 152 -> 64 B
Read string 30.48 ns -> 28.12 ns -7.7% 160 B, unchanged
The other twelve rows are the control: the change cannot reach a protocol
without strings and all of them stay within the run to run spread with byte
identical allocation.
The 160 bytes left on the string read are the POCO and the decoded string
itself, which is what the caller asked for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
What
The last per message allocation. A string field was encoded into a
List<byte>scratch buffer which was then copied into the frame, and for a sequence protocol the pre pass that assigns the length fields encoded every string a second time only to throw the result away.The encoder now writes into the span the frame itself occupies, and the pre pass only measures.
One case still needs a buffer of its own: a value longer than its fixed length field has to be cut at the byte level, and an encoder cannot write into a span that is too small for its output. That one is rented from the pool, and only values that overflow their field ever reach it.
Numbers
A/B of the same tree with and without the change, back to back:
The other twelve rows are the control: the change cannot reach a protocol without strings, and all of them stay within the run to run spread with byte identical allocation.
This also clears the one row that got ~6% slower in #15; it is now well below where it started.
The 160 bytes left on the string read are the POCO and the decoded string itself, which is what the caller asked for, so there is nothing left to remove there.
Safety
130/130 green, golden vectors unmoved. The behaviour this touches is exactly what
TestFixedLengthStringWritingpinned in #16 first: padding, byte level truncation including a split surrogate pair, odd declared lengths in both branches, empty and null values, and agreement between both write overloads. That was written before this change for precisely this reason.Where v4 ends up
Against the shipped 3.6.1 code:
At 100.000 messages per second the write path used to produce about 89 MB/s of garbage in little endian and 153 MB/s in big endian. It now produces 6.4 MB/s, or none at all into a reused buffer.
🤖 Generated with Claude Code