Repository navigation
Straight paths for Advance, NextElementBytes and AdvanceIter - #8
Merged
Merged
Conversation
walkObjects pushes every container it has yet to visit, not one per level, and it grew that stack from 64 entries again on every call: up to 6 MB per operation on canada. The objects rows measured the garbage collector as much as the walk - with every Iter copied through a write barrier while it marked - and moved by tens of percent between runs of the same binary. Kept between iterations, the stack allocates nothing: citm_catalog 365 -> 234 us, canada 1943 -> 979 us.
asp24
force-pushed
the
iter-frontend-2
branch
from
October 10, 2026 20:41
c08eaa6 to
9a418ec
Compare
ForEach and Array's helpers hand out every element as an Iter of its own, through AdvanceIter, and no benchmark called it. The iters rows descend each document that way.
A NOP of zero sent it back to the same entry by recursion until the stack ran out - a fatal error, not a panic a caller can recover from. A container whose end lies before its start - which Deserialize lets through when the stored distance is zero - gave an element ending before it starts, and moved the object back. Both are now errors, as they already were in AdvanceIter: the NOP skip is a loop that refuses a skip of zero, and the element size is checked before it is used. Neither comes from the parser: its NOPs skip at least one entry, and its containers end after they start.
Advance gets the shape AdvanceInto got in 404b6b1. The common case - an element inside the tape that is not a NOP - is one straight run, and whether a value word follows is a bit test, not calcNext's switch. A container is the one case with a second branch, to work out how far to skip, and the check that the skip does not go backwards stays on that branch. NOPs and the end of the tape go to the loop after it, unchanged. Against the parent, four code layouts a side, six rounds on twelve cores: IterRead objects -3.1% to -13.2%, on every document walk, typed, iters within the control advance_diff_test.go keeps the old Advance as a reference and walks both in lockstep, from every tag on the corpus, on the corpus cut short, and on it with container ends pointing backwards or past the tape.
NextElementBytes switched on the key's tag, and then twice on the value's, through calcNext. The common case - a key whose name lies within its area, then an element that ends within the tape - is now one straight run: a bit test for a value word, and a branch taken only for a container. The end of the object, NOPs and every error go to the code it had before, now nextElementBytes, from the start, so they behave as they did. Against the parent, four code layouts a side, six rounds on twelve cores: IterRead objects -4.5% to -13.4%; canada, with almost no keys, -0.1% walk, typed, iters within the control
Half of AdvanceIter's time went to one load. It set i's fields and then
copied *i into dst, and the copy read off and addNext in one 16-byte load
right behind the two 8-byte stores that wrote them. The store queue cannot
forward one load from two stores, so it waited for both to reach the
cache. The old code did the same through calcNext. dst now gets the tape
copied from i and its other fields from registers.
The common case is also one straight run now, as in Advance, and NOPs, the
end of the tape and every error go to the code it had before, now
advanceIter, from the start.
Against the parent, four code layouts a side, six rounds on twelve cores:
IterRead iters -43.6% to -50.5%, on every document: -14% to -17%
from the straight run, measured alone, and the rest
from not reading back
walk, typed, objects within the control
The read group as a whole moves -13.0% in geomean; ParseNDReuse and the
payloads within the control.
asp24
force-pushed
the
iter-frontend-2
branch
from
October 10, 2026 20:45
9a418ec to
1e03909
Compare
The list of what differs from upstream named parsing and nothing on the reading side. Measured against v0.4.5 side by side - BenchmarkIterRead added to its tree, four code layouts a side, six rounds on twelve cores - every one of the 28 rows is faster, geomean -31.7%: walk -29.7% to -39.1% typed -24.6% to -30.7% objects -12.5% to -22.5% iters -43.0% to -51.1% The NextElementBytes fix for corrupt tapes joins the list of fixed inputs.
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.
Applies the approach of #6 to the other three functions that walk the tape one element at a time:
Advance,NextElementBytesandAdvanceIter. Each now handles the common case in one straight run. NOPs, the end of the tape and every error go to the code the function had before, which runs from the start, so those cases behave as they did.Commits
Keep the object walk's stack between iterations.
IterRead/*/objectsregrew its stack on every call, up to 6 MB per operation oncanada. The rows mostly measured the garbage collector, with everyItercopied through a write barrier while it marked. The same binary moved by tens of percent between runs, and the first measurement of this change showed a regression that was not there. The stack is now kept between calls and allocates nothing:citm_catalog365 → 234 µs,canada1943 → 979 µs.Benchmark reading through
AdvanceIter.ForEachandArray's helpers read through it, and no benchmark called it. New rows:IterRead/*/iters.Stop
NextElementByteson two corrupt tapes, found by the differential test.Deserializeaccepts when the stored distance is zero, moved the object back.AdvanceIter. The parser never produces either.Advance: one straight path, asAdvanceIntohas.NextElementBytes: one straight path, with no switch on either tag.AdvanceIter: one straight path. It also stops reading back what it has just written:*dst = *ireadoffandaddNextin one 16-byte load right after the two 8-byte stores that wrote them. The store queue cannot forward that, so the load waited for both stores to reach the cache. That one load took half of the function's time.README. The list of differences from upstream gains reading. Against v0.4.5, measured side by side with
BenchmarkIterReadadded to its tree, all 28 rows are faster, geomean -31.7%:walk-30% to -39%,typed-25% to -31%,objects-12% to -22%,iters-43% to -51%.Time, each commit against its parent
Measured with
benchmarks/perfgate/ab.sh: four code layouts a side, six rounds, on twelve cores.IterReadrowsAdvanceobjectsNextElementBytesobjectscanada, almost no keys, -0.1%AdvanceIteritersThe
readgroup from commit 3 to the end: geomean -13.0% (control +2.1%).ParseNDReuseand the payloads are within the control.Testing
advance_diff_test.gokeeps the previous code of each function as a reference and walks both in lockstep. Walks start from every tag on:Every field of the
Iter,dstand theObjectis compared, along with the result, the error and any panic. Seeding faults into the new code checked the test: every one was caught, except one that is equivalent by construction (end < lenforend <= len, which only sends the edge case down the slow path).🤖 Generated with Claude Code