Repository navigation
Encoder: look at a short string and write it in one pass - #685
Merged
Merged
Conversation
A string of 1 to 31 bytes was looked at for a byte to escape and then copied from the beginning again at the end of AppendString: read twice, after a jump to the copy and loads of what the look had not kept in registers. In the encode of the small payload of the benchmark report, whose strings are 6 to 36 bytes, the strings took 0.72 s where the ones of sonic took 0.41 s on amd64. A string of up to maxOnePassLength bytes (31 on amd64 and on the architectures without SIMD, 15 on arm64, whose SIMD scan starts at 16) is now written to its place in the capacity of the buffer as it is looked at, by its bytes, the halves of a word or 2 to 4 words, and AppendString returns from there; what was written is left out of the buffer if the string has a byte to escape. The position is taken from the buffer again after the check for the escapes, so that the frame of AppendString is not larger than before. A longer string is scanned by scanBytesSIMD called from AppendString, which is inlined, instead of through hasEscapeSIMD, which was not: one call less. An empty string is appended before the options are looked at. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #685 +/- ##
==========================================
+ Coverage 88.17% 88.20% +0.03%
==========================================
Files 84 84
Lines 12905 12943 +38
==========================================
+ Hits 11379 11417 +38
Misses 1526 1526 🚀 New features to boost your workflow:
|
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.
Summary
A string was looked at for a byte to escape and then, at the end of
AppendString, copied from its beginning again: read twice, after a jump to the copy and loads of what the look had not kept in registers. In the encode of the small payload of the benchmark report, whose strings are 6 to 36 bytes, the strings took 0.72 s where the ones of sonic took 0.41 s on amd64.maxOnePassLengthbytes in one pass: a string of 1 to 3 bytes (by its bytes), 4 to 7 (by the two halves of a word) and 8 tomaxOnePassLength(by 2 to 4 overlapping words) is written to its place in the capacity of the buffer as it is looked at, andAppendStringreturns from there. If the string has a byte to escape, what was written is left out of the buffer and the escape of the options appends it as before.maxOnePassLengthis 31 on amd64 and on the architectures without SIMD, and 15 on arm64, whose SIMD scan starts at 16 bytes. A buffer without the capacity goes the way of before, which grows it.bufagain after the check for the escapes, which is kept across that call anyway, so that the frame ofAppendStringstays 0x80 bytes on amd64. A larger frame (0x90) moved the code of the unchanged paths and made them 2-5% slower.AppendStringcallsscanBytesSIMD, which is inlined, instead ofhasEscapeSIMD, which was not and is removed.BenchmarkAppendStringmeasures each length of 0 to 10000 bytes, 8 to 32 in detail, with the options of the std configuration (HTMLEscapeOption | NormalizeUTF8Option) and of the fastest one (none), andTestAppendStringFastPathcovers every length up to 72.Measurements
BenchmarkAppendStringon amd64 CI against master, 3 machines (EPYC 7763), the mean over 8 function layouts of the median of 5 rounds for the lengths outside 8 to 31 (4 layouts of 3 rounds for those); each binary was also built with a pad file (in parentheses), to separate the effect of the change from the one of the layout:Local arm64: -13% to -42% for 0 to 16 bytes, -0.2% to -9% for the longer ones.
The encode of the report payloads alone, amd64 CI, 3 machines (EPYC 7763 x2, EPYC 9V74), 3 layouts, against master: every payload of every configuration is faster,
large-8.7% to -11.2%,small-4.6% to -9.8%,twitter-any-4.1% to -9.3%,openai-2.8% to -8.0%,codeandgithub-0.4% to -4.5%. Against sonic:Test
TestAppendStringFastPath: every length up to 72, with each byte to escape at each position, against the escape of the options.go test ./...with go1.25 / go1.26 / go1.27.1 and-race, on amd64 too; the tests of the root, the encoder and the decoder on linux/s390x.make lint.🤖 Generated with Claude Code