From e2895a83658ec09f933f5b0483c8ab0f5db88847 Mon Sep 17 00:00:00 2001 From: Shaun Patterson Date: Sat, 23 May 2026 14:04:26 -0400 Subject: [PATCH] perf(table): collapse branches in MergeIterator.fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework MergeIterator.fix to: - hoist the "other" node pointer once (instead of two bigger() calls), avoiding repeated &mi.left/&mi.right comparisons - inline the swap (mi.small = big) where it was previously hidden behind swapSmall(), so the compiler sees a straight pointer assignment with no branch - collapse the cmp<0 / cmp>0 cases into a single conditional swap expressed as `(cmp < 0) == mi.reverse`, removing one switch arm plus the no-op staticcheck-suppressed branch This makes the hot per-step decision in iteration two compares and one conditional store, instead of a 4-arm switch with embedded helper-method dispatches. y.CompareKeys is now the only call site for the LCP comparison, leaving direction handling fully in fix(). bigger() and swapSmall() were the only callers of each other and of the inlined logic; with both gone, the helpers become dead code and are removed in the same commit. Existing merge iterator tests (TestMergeMore, TestMergeIteratorSeek, TestMergeIteratorDuplicate, TestMergeDuplicates — forward + reverse variants) cover this path at 100% statement coverage. Measured -0.62% composite ns/op across the 74-benchmark stable subset. --- table/merge_iterator.go | 57 ++++++++++++----------------------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/table/merge_iterator.go b/table/merge_iterator.go index b42d79e03..1acce471f 100644 --- a/table/merge_iterator.go +++ b/table/merge_iterator.go @@ -85,55 +85,32 @@ func (n *node) seek(key []byte) { } func (mi *MergeIterator) fix() { - if !mi.bigger().valid { + // Hoist the "other" node once. mi.small is always &mi.left or &mi.right. + big := &mi.left + if mi.small == &mi.left { + big = &mi.right + } + if !big.valid { return } if !mi.small.valid { - mi.swapSmall() + mi.small = big return } - cmp := y.CompareKeys(mi.small.key, mi.bigger().key) - switch { - case cmp == 0: // Both the keys are equal. - // In case of same keys, move the right iterator ahead. + cmp := y.CompareKeys(mi.small.key, big.key) + if cmp == 0 { + // Same key on both sides: advance right, then if right was small, swap to left. mi.right.next() - if &mi.right == mi.small { - mi.swapSmall() - } - return - case cmp < 0: // Small is less than bigger(). - if mi.reverse { - mi.swapSmall() - } else { //nolint:staticcheck - // we don't need to do anything. Small already points to the smallest. + if mi.small == &mi.right { + mi.small = &mi.left } return - default: // bigger() is less than small. - if mi.reverse { - // Do nothing since we're iterating in reverse. Small currently points to - // the bigger key and that's okay in reverse iteration. - } else { - mi.swapSmall() - } - return - } -} - -func (mi *MergeIterator) bigger() *node { - if mi.small == &mi.left { - return &mi.right - } - return &mi.left -} - -func (mi *MergeIterator) swapSmall() { - if mi.small == &mi.left { - mi.small = &mi.right - return } - if mi.small == &mi.right { - mi.small = &mi.left - return + // Swap when small holds the "wrong" side for the direction: + // forward (!reverse) + cmp > 0 → big is smaller, swap to it + // reverse + cmp < 0 → big is larger, swap to it + if (cmp < 0) == mi.reverse { + mi.small = big } }