Repository navigation
Read the next element before ForEach calls fn - #9
Merged
Merged
Conversation
ParsedJson, Object and Array each have a ForEach. Each ForEach gives every element to a callback by value. Before this change, no benchmark used these functions. The new foreach rows go into all containers of each document through ForEach. They use one Object and one Array for all depths, and they do not allocate memory.
The ForEach functions of Array and Object give fn its Iter by value.
Because of this, each call copied the Iter immediately after Advance wrote
it. The copy used 16-byte loads on 8-byte stores, and the store queue
cannot forward such loads. AdvanceIter had the same stall. On canada,
which has many arrays of two elements, the IPC was 2.4. On the same tape,
the iters walk had an IPC of 5.4.
Now the loops read the next element before they call fn for the current
element. fn gets a copy from before that read, and the stores of the copy
are already in the cache. The loop does the read itself, not Advance. For
objects, two calls to Advance for each member cost more than the stall: a
version with these calls made twitter 8% slower. The first Iter gets one
field at a time, because Iter.Array and Iter.Object write their fields in
that way.
Because fn can write to the tape, the loop keeps a read only if its words
did not change after fn. An element uses one word, and a member uses
three. If a word changed, the old step runs after fn. The old step also
does NOPs, errors and the end of the tape. With a key filter,
Object.ForEach keeps its old loop. That loop stops at the last key in the
filter, and a read after that key reads all of the remaining object.
foreach_diff_test.go compares the two functions with the old loops. It
starts from each tag on the tapes of the Advance tests. Some callbacks
write to the tape: to their element, to the next element, to its value
bits, to the length of a key and on NOPs. The test failed for each of
thirteen intentional errors in the new code. These errors include each
check in the guard and in the read.
Measured against the parent, with four code layouts for each side, six
rounds and twelve cores:
IterRead foreach geomean -29.9%, on every document:
canada -34.0%, apache_builds -31.8%, citm_catalog
-30.9%, twitter -29.6%, instruments -28.3%,
update-center -27.9%, github_events -26.9%
rest of read -1.3% to +3.3%, none past its threshold
This was referenced Oct 11, 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 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.
The
ForEachfunctions ofArrayandObjectgive the callback itsIterby value. Each call copied theIterimmediately afterAdvancewrote it. This is the store-forwarding stall that #8 removed fromAdvanceIter. This change removes it fromForEach.Commits
ForEach. Before this change, no benchmark usedForEach. The new rowsIterRead/*/foreachgo into all containers of each document through theForEachcallbacks. They do not allocate memory.ForEachcallsfn.Iterused 16-byte loads on the 8-byte stores ofAdvance. The store queue cannot forward such loads, and they wait for the cache. Oncanada, the IPC was 2.4. On the same tape, theiterswalk had an IPC of 5.4.fnfor the current element.fngets a copy from before that read.Advance. A version with two calls toAdvancefor each object member madetwitter8% slower.fncan write to the tape, the loop keeps a read only if its words did not change afterfn: one word for an element, three words for a member. If a word changed, the old step runs afterfn. The old step also does NOPs, errors and the end of the tape.Object.ForEachkeeps its old loop. That loop stops at the last key in the filter. A read after that key reads all of the remaining object.Time, against the parent
Measured with
benchmarks/perfgate/ab.sh: four code layouts for each side, six rounds, twelve cores.IterRead/*/foreachThe other rows of the
readgroup change by -1.3% to +3.3%. No row is past its threshold. The allocations do not change.Testing
foreach_diff_test.gokeeps the old loops as references and compares them with the new functions. The walks start from each tag on the tapes ofadvance_diff_test.go: the corpus, the corpus with deletions, the corpus cut short, the corpus with bad container ends, and edge tapes. A new edge tape has a root as the value of a member.Some callbacks write to the tape:
fn;The test compares each
Iterthatfngets, the error, each panic and the tape after the walk. The test failed for each of thirteen intentional errors in the new code. These errors include each check in the guard and in the read.Found, not changed here
AdvanceandAdvanceIterskip one word too many after a NOP.DeleteElems,SetNullandDeserializecount a NOP skip from the NOP word.AdvanceInto,PeekNextandNextElementBytesread it in the same way.AdvanceandAdvanceItercount it from the next word. Example:DeleteElemsremoves2from[1,2,3,4], and thenArray.ForEachgives only1. Upstream has the same code. A different PR will correct it. This PR keeps the current behavior ofAdvance.🤖 Generated with Claude Code