Skip to content

perf(skl): embed Iterator by value in UniIterator#2288

Open
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/skl-uniiterator-embed
Open

perf(skl): embed Iterator by value in UniIterator#2288
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/skl-uniiterator-embed

Conversation

@shaunpatterson

Copy link
Copy Markdown
Contributor

Summary

Halves the per-memtable iterator construction allocation count by embedding the inner Iterator inside UniIterator instead of holding a *Iterator. Previously NewUniIterator allocated twice: once for the inner Iterator (via NewIterator) and once for the UniIterator wrapper. Embedding by value collapses these into a single heap allocation.

Extracts the shared (*Iterator).init helper so future setup added in NewIterator applies uniformly to UniIterator's embedded construction path.

Motivation

In dgraph's posting list rollup, ~6 UniIterators are constructed per cache miss (1 mutable + 5 immutable memtables). This scales to roughly 6 fewer allocs per rollup miss.

Benchmark — BenchmarkRollupKeyIterator

B/op allocs/op
before 826 16
after 817 15

Test plan

  • go test ./skl/... passes
  • go test ./... passes
  • Existing iterator tests cover the embedded value semantics

🤖 Generated with Claude Code

@shaunpatterson shaunpatterson requested a review from a team as a code owner May 26, 2026 00:43
@shaunpatterson shaunpatterson force-pushed the perf/skl-uniiterator-embed branch from 499a5af to 2881ce3 Compare June 19, 2026 14:10
Eliminates the UniIterator wrapper struct entirely. UniIterator existed
solely to wrap Iterator (which already implements every method of
y.Iterator) and dispatch Next/Seek/Rewind to Prev/SeekForPrev/SeekToLast
when a 'reversed' flag was set.

The reversed flag moves onto Iterator directly:

  type Iterator struct {
      list     *Skiplist
      n        *node
      reversed bool
  }

Iterator.Next/Seek/Rewind dispatch to their backward counterparts when
reversed is true, so the same *Iterator satisfies y.Iterator in both
directions. NewUniIterator returns *Iterator (the function name is kept
for compatibility — it now does the IncrRef + struct construction
inlined, eliminating the UniIterator wrapper allocation).

Net: one less heap allocation per NewUniIterator call. Eliminates the
embed-by-value vs named-field footgun from the original PR (it goes
away — there is no longer a struct-of-Iterator; the Iterator IS the
unidirectional iterator when needed).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shaunpatterson shaunpatterson force-pushed the perf/skl-uniiterator-embed branch from 2881ce3 to 91cf0fd Compare July 14, 2026 01:36
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