perf: copy string and binary values out of the reader buffer - #43
Merged
Conversation
unpackString and unpackBinary filled their allocation with readSliceAll, which
goes through readSliceShort and loops to handle partial reads. On the
decodeFromSlice path the reader is a Reader.fixed whose buffer holds the whole
message, so the bytes are always contiguous and present and that machinery is
never needed.
readSliceFast memcpys straight out of the buffer when the value is fully
buffered, and falls back to readSliceAll otherwise, so a streaming reader whose
buffer cannot hold the value still works. Four call sites: the allocating and
into-buffer forms of both string and binary.
Measured with callgrind, instructions per decode:
case old new
event 1746.74 1621.74 -7.2% struct with 7 short strings
strings 5138.68 4914.68 -4.4% array of 32 strings
long 461.29 424.29 -8.0% one 2000-byte string
plain 519.01 517.01 -0.4% no strings, control
Repeated at ReleaseSafe: -6.7%, -2.7%, -0.2%, +0.4%. The saving is a fixed
amount per string value rather than a proportion, so it shows up largest where
strings are short and numerous, and washes out when a single large memcpy
dominates -- which is why `long` moves so much between the two builds.
The control stays flat in both, which is what makes the rest readable.
|
Warning Review limit reachedNext included review available in 5 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Fixes #32.
unpackStringandunpackBinaryfilled their allocation withreadSliceAll, which goes throughreadSliceShortand loops to handle partial reads. On thedecodeFromSlicepath the reader is aReader.fixedwhose buffer holds the whole message, so the bytes are always contiguous and present — that machinery never does anything.readSliceFastmemcpys straight out of the buffer when the value is fully buffered, and falls back toreadSliceAllotherwise so a streaming reader whose buffer cannot hold the value still works. Four call sites: the allocating and into-buffer forms of both string and binary.Measurement
Callgrind, instructions per decode, one library version per binary:
eventstringslongplainRepeated at ReleaseSafe: −6.7%, −2.7%, −0.2%, +0.4%. The control stays flat in both, which is what makes the rest readable.
The saving is a fixed amount per string value, not a proportion — roughly 8–20 instructions of loop setup. So it shows up largest where strings are short and numerous, and washes out when one large memcpy dominates the cost. That's why
longmoves so much between the two builds: the same absolute saving against a 461-instruction baseline in ReleaseFast and a 3345-instruction one in ReleaseSafe.Smaller than #38's 18%, but consistent in direction across builds with flat controls.
Tests
Two unit tests on
readSliceFast— one for the fast path (checks the bytes are consumed, not just peeked) and one that forces the fallback by shortening the reader'send, since a fixed reader cannot refill and therefore surfacesEndOfStreamif and only if the fallback was taken.One thing I could not test
I wanted an end-to-end test decoding a string larger than a streaming reader's buffer.
TrickleReader, the existing helper for that, hangs forever on it: itsreadVecignores thedatavectors it is handed and returns 0 once its own buffer fills, soreadSliceAllinto a larger destination spins. Its doc comment claims to model "the reader's buffer can be smaller than the value being decoded", and the existing tests only ever use it below that threshold or to assert an error.That is pre-existing and unrelated to this change, but it is a live trap for the next person who writes that test. Filing separately.
zig build test: 185/185 pass, up from 183.