Skip to content

perf(iterator): pre-size iters slice in Txn.NewIterator#2290

Open
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/iterator-iters-presize
Open

perf(iterator): pre-size iters slice in Txn.NewIterator#2290
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/iterator-iters-presize

Conversation

@shaunpatterson
Copy link
Copy Markdown

Summary

The iters slice in Txn.NewIterator was previously grown via append-from-nil, hitting growslice on every memtable iterator appended. In dgraph's posting list rollup with 6 memtables this hits 3 growslice events (0→1→2→4→8) just to fit the deterministic memtable-pass before appendIterators adds the level iterators.

Computing the cap up front (len(tables) plus 1 if a pending-writes iterator exists, 0 otherwise) collapses those growslices into a single make() of exactly the needed size.

Level iterators added by appendIterators may still trigger a growslice on first append past the initial cap; that's preserved as-is to avoid speculatively over-allocating for callers that don't have any level iterators.

Benchmark — BenchmarkRollupKeyIterator

B/op allocs/op
before 817 15
after 801 14

In a 6-memtable workload the savings scale up: original code hits 3 growslice paths to fit just the memtables; pre-sizing skips all of them.

Test plan

  • go test ./... passes
  • Existing iterator tests cover the path

🤖 Generated with Claude Code

The iters slice was previously grown via append-from-nil, hitting
growslice on every memtable iterator appended. In dgraph's posting
list rollup with 6 memtables this hits 3 growslice events (0→1→2→
4→8) just to fit the deterministic memtable-pass before
appendIterators adds the level iterators.

Computing the cap up front (len(tables) plus 1 if a pending-writes
iterator exists, 0 otherwise) collapses those growslices into a
single make() of exactly the needed size. Level iterators added by
appendIterators may still trigger a growslice on first append past
the initial cap; that's preserved as-is to avoid speculatively
over-allocating for callers that don't have any level iterators.

Saves 1 alloc and 16 B per iterator construction on
BenchmarkRollupKeyIterator:

  before:  817 B/op, 15 allocs/op
  after:   801 B/op, 14 allocs/op

In a 6-memtable workload the savings scale up: original code hits
3 growslice paths to fit just the memtables; pre-sizing skips all
of them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shaunpatterson shaunpatterson requested a review from a team as a code owner May 26, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant