From d25fdb581e403873e43adb49658396f9b1166369 Mon Sep 17 00:00:00 2001 From: Nathan VanBenschoten Date: Tue, 25 Feb 2020 20:47:29 -0500 Subject: [PATCH] interval/generic: avoid letting argument to FirstOverlap escape to heap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to #45276. Prior to this change, the argument provided to `iterator.FirstOverlap` was stored in the iterator. This caused the argument to escape to the heap (if the parameterized type was a pointer) and would force an allocation if the argument was not already on the heap. This was not the intention and was causing issues in #45276. This commit fixes the issue by no longer storing the argument in the iterator's `overlapScan` object. This allows escape analysis to determine that the argument does not escape, like it was already doing for `iterator.SeekGE` and `iterator.SeekLT`. ``` name old time/op new time/op delta BTreeIterFirstOverlap/count=16-16 400ns ± 1% 325ns ± 5% -18.75% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=128-16 661ns ± 0% 581ns ± 1% -12.02% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=1024-16 1.15µs ± 2% 1.07µs ± 2% -6.76% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=8192-16 1.84µs ± 2% 1.76µs ± 2% -4.38% (p=0.016 n=5+5) BTreeIterFirstOverlap/count=65536-16 2.62µs ± 2% 2.52µs ± 1% -4.07% (p=0.008 n=5+5) BTreeIterNextOverlap-16 11.1ns ± 2% 10.7ns ± 0% -3.95% (p=0.016 n=5+4) BTreeIterOverlapScan-16 34.2µs ± 1% 31.2µs ± 1% -8.78% (p=0.008 n=5+5) name old alloc/op new alloc/op delta BTreeIterFirstOverlap/count=16-16 96.0B ± 0% 0.0B -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=128-16 96.0B ± 0% 0.0B -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=1024-16 96.0B ± 0% 0.0B -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=8192-16 96.0B ± 0% 0.0B -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=65536-16 96.0B ± 0% 0.0B -100.00% (p=0.008 n=5+5) BTreeIterNextOverlap-16 0.00B 0.00B ~ (all equal) BTreeIterOverlapScan-16 144B ± 0% 48B ± 0% -66.67% (p=0.008 n=5+5) name old allocs/op new allocs/op delta BTreeIterFirstOverlap/count=16-16 1.00 ± 0% 0.00 -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=128-16 1.00 ± 0% 0.00 -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=1024-16 1.00 ± 0% 0.00 -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=8192-16 1.00 ± 0% 0.00 -100.00% (p=0.008 n=5+5) BTreeIterFirstOverlap/count=65536-16 1.00 ± 0% 0.00 -100.00% (p=0.008 n=5+5) BTreeIterNextOverlap-16 0.00 0.00 ~ (all equal) BTreeIterOverlapScan-16 6.40 ± 9% 5.00 ± 0% -21.88% (p=0.008 n=5+5) ``` --- pkg/storage/spanlatch/latch_interval_btree.go | 37 +++++++--------- .../spanlatch/latch_interval_btree_test.go | 43 +++++++++---------- pkg/storage/spanlatch/manager.go | 2 +- .../generic/example_interval_btree.go | 37 +++++++--------- .../generic/example_interval_btree_test.go | 43 +++++++++---------- .../generic/internal/interval_btree_tmpl.go | 37 +++++++--------- .../internal/interval_btree_tmpl_test.go | 43 +++++++++---------- 7 files changed, 109 insertions(+), 133 deletions(-) diff --git a/pkg/storage/spanlatch/latch_interval_btree.go b/pkg/storage/spanlatch/latch_interval_btree.go index 9ec5e6e56061..6c4760df0fb1 100644 --- a/pkg/storage/spanlatch/latch_interval_btree.go +++ b/pkg/storage/spanlatch/latch_interval_btree.go @@ -1038,8 +1038,6 @@ func (i *iterator) Cur() *latch { // It does so because the item at this position is the first item with a // start key larger than the search range's end key. type overlapScan struct { - item *latch // search item - // The "soft" lower-bound constraint. constrMinN *node constrMinPos int16 @@ -1058,29 +1056,24 @@ func (i *iterator) FirstOverlap(item *latch) { return } i.pos = 0 - i.o = overlapScan{item: item} - i.constrainMinSearchBounds() - i.constrainMaxSearchBounds() - i.findNextOverlap() + i.o = overlapScan{} + i.constrainMinSearchBounds(item) + i.constrainMaxSearchBounds(item) + i.findNextOverlap(item) } // NextOverlap positions the iterator to the item immediately following // its current position that overlaps with the search item. -func (i *iterator) NextOverlap() { +func (i *iterator) NextOverlap(item *latch) { if i.n == nil { return } - if i.o.item == nilT { - // Invalid. Mixed overlap scan with non-overlap scan. - i.pos = i.n.count - return - } i.pos++ - i.findNextOverlap() + i.findNextOverlap(item) } -func (i *iterator) constrainMinSearchBounds() { - k := i.o.item.Key() +func (i *iterator) constrainMinSearchBounds(item *latch) { + k := item.Key() j := sort.Search(int(i.n.count), func(j int) bool { return bytes.Compare(k, i.n.items[j].Key()) <= 0 }) @@ -1088,8 +1081,8 @@ func (i *iterator) constrainMinSearchBounds() { i.o.constrMinPos = int16(j) } -func (i *iterator) constrainMaxSearchBounds() { - up := upperBound(i.o.item) +func (i *iterator) constrainMaxSearchBounds(item *latch) { + up := upperBound(item) j := sort.Search(int(i.n.count), func(j int) bool { return !up.contains(i.n.items[j]) }) @@ -1097,24 +1090,24 @@ func (i *iterator) constrainMaxSearchBounds() { i.o.constrMaxPos = int16(j) } -func (i *iterator) findNextOverlap() { +func (i *iterator) findNextOverlap(item *latch) { for { if i.pos > i.n.count { // Iterate up tree. i.ascend() } else if !i.n.leaf { // Iterate down tree. - if i.o.constrMinReached || i.n.children[i.pos].max.contains(i.o.item) { + if i.o.constrMinReached || i.n.children[i.pos].max.contains(item) { par := i.n pos := i.pos i.descend(par, pos) // Refine the constraint bounds, if necessary. if par == i.o.constrMinN && pos == i.o.constrMinPos { - i.constrainMinSearchBounds() + i.constrainMinSearchBounds(item) } if par == i.o.constrMaxN && pos == i.o.constrMaxPos { - i.constrainMaxSearchBounds() + i.constrainMaxSearchBounds(item) } continue } @@ -1140,7 +1133,7 @@ func (i *iterator) findNextOverlap() { // span's start key. return } - if upperBound(i.n.items[i.pos]).contains(i.o.item) { + if upperBound(i.n.items[i.pos]).contains(item) { return } } diff --git a/pkg/storage/spanlatch/latch_interval_btree_test.go b/pkg/storage/spanlatch/latch_interval_btree_test.go index 66623062acba..8bc40e530f5f 100644 --- a/pkg/storage/spanlatch/latch_interval_btree_test.go +++ b/pkg/storage/spanlatch/latch_interval_btree_test.go @@ -224,7 +224,7 @@ func checkIter(t *testing.T, it iterator, start, end int, spanMemo map[int]roach } all := newItem(spanWithEnd(start, end)) - for it.FirstOverlap(all); it.Valid(); it.NextOverlap() { + for it.FirstOverlap(all); it.Valid(); it.NextOverlap(all) { item := it.Cur() expected := spanWithMemo(i, spanMemo) if !expected.Equal(spanFromItem(item)) { @@ -344,7 +344,8 @@ func TestBTreeSeekOverlap(t *testing.T) { // Iterate over overlaps with a point scan. it := tr.MakeIter() for i := 0; i < count+size; i++ { - it.FirstOverlap(newItem(spanWithEnd(i, i))) + scanItem := newItem(spanWithEnd(i, i)) + it.FirstOverlap(scanItem) for j := 0; j < size+1; j++ { expStart := i - size + j if expStart < 0 { @@ -363,7 +364,7 @@ func TestBTreeSeekOverlap(t *testing.T) { t.Fatalf("%d: expected %s, but found %s", i, expected, spanFromItem(item)) } - it.NextOverlap() + it.NextOverlap(scanItem) } if it.Valid() { t.Fatalf("%d: expected invalid iterator %v", i, it.Cur()) @@ -377,7 +378,8 @@ func TestBTreeSeekOverlap(t *testing.T) { // Iterate over overlaps with a range scan. it = tr.MakeIter() for i := 0; i < count+size; i++ { - it.FirstOverlap(newItem(spanWithEnd(i, i+size+1))) + scanItem := newItem(spanWithEnd(i, i+size+1)) + it.FirstOverlap(scanItem) for j := 0; j < 2*size+1; j++ { expStart := i - size + j if expStart < 0 { @@ -396,7 +398,7 @@ func TestBTreeSeekOverlap(t *testing.T) { t.Fatalf("%d: expected %s, but found %s", i, expected, spanFromItem(item)) } - it.NextOverlap() + it.NextOverlap(scanItem) } if it.Valid() { t.Fatalf("%d: expected invalid iterator %v", i, it.Cur()) @@ -436,14 +438,14 @@ func TestBTreeSeekOverlapRandom(t *testing.T) { const scanTrials = 100 for j := 0; j < scanTrials; j++ { - var scanLa *latch + var scanItem *latch scanStart := rng.Intn(count) scanEnd := rng.Intn(count + 10) if scanEnd <= scanStart { scanEnd = scanStart - scanLa = newItem(spanWithEnd(scanStart, scanEnd)) + scanItem = newItem(spanWithEnd(scanStart, scanEnd)) } else { - scanLa = newItem(spanWithEnd(scanStart, scanEnd+1)) + scanItem = newItem(spanWithEnd(scanStart, scanEnd+1)) } var exp, found []*latch @@ -454,13 +456,13 @@ func TestBTreeSeekOverlapRandom(t *testing.T) { } it := tr.MakeIter() - it.FirstOverlap(scanLa) + it.FirstOverlap(scanItem) for it.Valid() { found = append(found, it.Cur()) - it.NextOverlap() + it.NextOverlap(scanItem) } - require.Equal(t, len(exp), len(found), "search for %v", spanFromItem(scanLa)) + require.Equal(t, len(exp), len(found), "search for %v", spanFromItem(scanItem)) } } } @@ -796,6 +798,7 @@ func BenchmarkBTreeMakeIter(b *testing.B) { // BenchmarkBTreeIterSeekGE measures the cost of seeking a btree iterator // forward. func BenchmarkBTreeIterSeekGE(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -806,12 +809,10 @@ func BenchmarkBTreeIterSeekGE(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { s := spans[rng.Intn(len(spans))] + it := tr.MakeIter() it.SeekGE(newItem(s)) if testing.Verbose() { if !it.Valid() { @@ -828,6 +829,7 @@ func BenchmarkBTreeIterSeekGE(b *testing.B) { // BenchmarkBTreeIterSeekLT measures the cost of seeking a btree iterator // backward. func BenchmarkBTreeIterSeekLT(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -838,13 +840,11 @@ func BenchmarkBTreeIterSeekLT(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { j := rng.Intn(len(spans)) s := spans[j] + it := tr.MakeIter() it.SeekLT(newItem(s)) if testing.Verbose() { if j == 0 { @@ -868,6 +868,7 @@ func BenchmarkBTreeIterSeekLT(b *testing.B) { // BenchmarkBTreeIterFirstOverlap measures the cost of finding a single // overlapping item using a btree iterator. func BenchmarkBTreeIterFirstOverlap(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -878,13 +879,11 @@ func BenchmarkBTreeIterFirstOverlap(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { j := rng.Intn(len(spans)) s := spans[j] + it := tr.MakeIter() it.FirstOverlap(newItem(s)) if testing.Verbose() { if !it.Valid() { @@ -961,7 +960,7 @@ func BenchmarkBTreeIterNextOverlap(b *testing.B) { if !it.Valid() { it.FirstOverlap(allCmd) } - it.NextOverlap() + it.NextOverlap(allCmd) } } @@ -983,7 +982,7 @@ func BenchmarkBTreeIterOverlapScan(b *testing.B) { it := tr.MakeIter() it.FirstOverlap(item) for it.Valid() { - it.NextOverlap() + it.NextOverlap(item) } } } diff --git a/pkg/storage/spanlatch/manager.go b/pkg/storage/spanlatch/manager.go index ef5cb8e46fc1..653a87ebd240 100644 --- a/pkg/storage/spanlatch/manager.go +++ b/pkg/storage/spanlatch/manager.go @@ -368,7 +368,7 @@ func (m *Manager) wait(ctx context.Context, lg *Guard, snap snapshot) error { func (m *Manager) iterAndWait( ctx context.Context, t *timeutil.Timer, it *iterator, wait *latch, ignore ignoreFn, ) error { - for it.FirstOverlap(wait); it.Valid(); it.NextOverlap() { + for it.FirstOverlap(wait); it.Valid(); it.NextOverlap(wait) { held := it.Cur() if held.done.signaled() { continue diff --git a/pkg/util/interval/generic/example_interval_btree.go b/pkg/util/interval/generic/example_interval_btree.go index 0669eadaf08a..2b15f77f4f42 100644 --- a/pkg/util/interval/generic/example_interval_btree.go +++ b/pkg/util/interval/generic/example_interval_btree.go @@ -1038,8 +1038,6 @@ func (i *iterator) Cur() *example { // It does so because the item at this position is the first item with a // start key larger than the search range's end key. type overlapScan struct { - item *example // search item - // The "soft" lower-bound constraint. constrMinN *node constrMinPos int16 @@ -1058,29 +1056,24 @@ func (i *iterator) FirstOverlap(item *example) { return } i.pos = 0 - i.o = overlapScan{item: item} - i.constrainMinSearchBounds() - i.constrainMaxSearchBounds() - i.findNextOverlap() + i.o = overlapScan{} + i.constrainMinSearchBounds(item) + i.constrainMaxSearchBounds(item) + i.findNextOverlap(item) } // NextOverlap positions the iterator to the item immediately following // its current position that overlaps with the search item. -func (i *iterator) NextOverlap() { +func (i *iterator) NextOverlap(item *example) { if i.n == nil { return } - if i.o.item == nilT { - // Invalid. Mixed overlap scan with non-overlap scan. - i.pos = i.n.count - return - } i.pos++ - i.findNextOverlap() + i.findNextOverlap(item) } -func (i *iterator) constrainMinSearchBounds() { - k := i.o.item.Key() +func (i *iterator) constrainMinSearchBounds(item *example) { + k := item.Key() j := sort.Search(int(i.n.count), func(j int) bool { return bytes.Compare(k, i.n.items[j].Key()) <= 0 }) @@ -1088,8 +1081,8 @@ func (i *iterator) constrainMinSearchBounds() { i.o.constrMinPos = int16(j) } -func (i *iterator) constrainMaxSearchBounds() { - up := upperBound(i.o.item) +func (i *iterator) constrainMaxSearchBounds(item *example) { + up := upperBound(item) j := sort.Search(int(i.n.count), func(j int) bool { return !up.contains(i.n.items[j]) }) @@ -1097,24 +1090,24 @@ func (i *iterator) constrainMaxSearchBounds() { i.o.constrMaxPos = int16(j) } -func (i *iterator) findNextOverlap() { +func (i *iterator) findNextOverlap(item *example) { for { if i.pos > i.n.count { // Iterate up tree. i.ascend() } else if !i.n.leaf { // Iterate down tree. - if i.o.constrMinReached || i.n.children[i.pos].max.contains(i.o.item) { + if i.o.constrMinReached || i.n.children[i.pos].max.contains(item) { par := i.n pos := i.pos i.descend(par, pos) // Refine the constraint bounds, if necessary. if par == i.o.constrMinN && pos == i.o.constrMinPos { - i.constrainMinSearchBounds() + i.constrainMinSearchBounds(item) } if par == i.o.constrMaxN && pos == i.o.constrMaxPos { - i.constrainMaxSearchBounds() + i.constrainMaxSearchBounds(item) } continue } @@ -1140,7 +1133,7 @@ func (i *iterator) findNextOverlap() { // span's start key. return } - if upperBound(i.n.items[i.pos]).contains(i.o.item) { + if upperBound(i.n.items[i.pos]).contains(item) { return } } diff --git a/pkg/util/interval/generic/example_interval_btree_test.go b/pkg/util/interval/generic/example_interval_btree_test.go index 0f1d50ba1b5e..013fd29f1869 100644 --- a/pkg/util/interval/generic/example_interval_btree_test.go +++ b/pkg/util/interval/generic/example_interval_btree_test.go @@ -224,7 +224,7 @@ func checkIter(t *testing.T, it iterator, start, end int, spanMemo map[int]roach } all := newItem(spanWithEnd(start, end)) - for it.FirstOverlap(all); it.Valid(); it.NextOverlap() { + for it.FirstOverlap(all); it.Valid(); it.NextOverlap(all) { item := it.Cur() expected := spanWithMemo(i, spanMemo) if !expected.Equal(spanFromItem(item)) { @@ -344,7 +344,8 @@ func TestBTreeSeekOverlap(t *testing.T) { // Iterate over overlaps with a point scan. it := tr.MakeIter() for i := 0; i < count+size; i++ { - it.FirstOverlap(newItem(spanWithEnd(i, i))) + scanItem := newItem(spanWithEnd(i, i)) + it.FirstOverlap(scanItem) for j := 0; j < size+1; j++ { expStart := i - size + j if expStart < 0 { @@ -363,7 +364,7 @@ func TestBTreeSeekOverlap(t *testing.T) { t.Fatalf("%d: expected %s, but found %s", i, expected, spanFromItem(item)) } - it.NextOverlap() + it.NextOverlap(scanItem) } if it.Valid() { t.Fatalf("%d: expected invalid iterator %v", i, it.Cur()) @@ -377,7 +378,8 @@ func TestBTreeSeekOverlap(t *testing.T) { // Iterate over overlaps with a range scan. it = tr.MakeIter() for i := 0; i < count+size; i++ { - it.FirstOverlap(newItem(spanWithEnd(i, i+size+1))) + scanItem := newItem(spanWithEnd(i, i+size+1)) + it.FirstOverlap(scanItem) for j := 0; j < 2*size+1; j++ { expStart := i - size + j if expStart < 0 { @@ -396,7 +398,7 @@ func TestBTreeSeekOverlap(t *testing.T) { t.Fatalf("%d: expected %s, but found %s", i, expected, spanFromItem(item)) } - it.NextOverlap() + it.NextOverlap(scanItem) } if it.Valid() { t.Fatalf("%d: expected invalid iterator %v", i, it.Cur()) @@ -436,14 +438,14 @@ func TestBTreeSeekOverlapRandom(t *testing.T) { const scanTrials = 100 for j := 0; j < scanTrials; j++ { - var scanLa *example + var scanItem *example scanStart := rng.Intn(count) scanEnd := rng.Intn(count + 10) if scanEnd <= scanStart { scanEnd = scanStart - scanLa = newItem(spanWithEnd(scanStart, scanEnd)) + scanItem = newItem(spanWithEnd(scanStart, scanEnd)) } else { - scanLa = newItem(spanWithEnd(scanStart, scanEnd+1)) + scanItem = newItem(spanWithEnd(scanStart, scanEnd+1)) } var exp, found []*example @@ -454,13 +456,13 @@ func TestBTreeSeekOverlapRandom(t *testing.T) { } it := tr.MakeIter() - it.FirstOverlap(scanLa) + it.FirstOverlap(scanItem) for it.Valid() { found = append(found, it.Cur()) - it.NextOverlap() + it.NextOverlap(scanItem) } - require.Equal(t, len(exp), len(found), "search for %v", spanFromItem(scanLa)) + require.Equal(t, len(exp), len(found), "search for %v", spanFromItem(scanItem)) } } } @@ -796,6 +798,7 @@ func BenchmarkBTreeMakeIter(b *testing.B) { // BenchmarkBTreeIterSeekGE measures the cost of seeking a btree iterator // forward. func BenchmarkBTreeIterSeekGE(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -806,12 +809,10 @@ func BenchmarkBTreeIterSeekGE(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { s := spans[rng.Intn(len(spans))] + it := tr.MakeIter() it.SeekGE(newItem(s)) if testing.Verbose() { if !it.Valid() { @@ -828,6 +829,7 @@ func BenchmarkBTreeIterSeekGE(b *testing.B) { // BenchmarkBTreeIterSeekLT measures the cost of seeking a btree iterator // backward. func BenchmarkBTreeIterSeekLT(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -838,13 +840,11 @@ func BenchmarkBTreeIterSeekLT(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { j := rng.Intn(len(spans)) s := spans[j] + it := tr.MakeIter() it.SeekLT(newItem(s)) if testing.Verbose() { if j == 0 { @@ -868,6 +868,7 @@ func BenchmarkBTreeIterSeekLT(b *testing.B) { // BenchmarkBTreeIterFirstOverlap measures the cost of finding a single // overlapping item using a btree iterator. func BenchmarkBTreeIterFirstOverlap(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -878,13 +879,11 @@ func BenchmarkBTreeIterFirstOverlap(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { j := rng.Intn(len(spans)) s := spans[j] + it := tr.MakeIter() it.FirstOverlap(newItem(s)) if testing.Verbose() { if !it.Valid() { @@ -961,7 +960,7 @@ func BenchmarkBTreeIterNextOverlap(b *testing.B) { if !it.Valid() { it.FirstOverlap(allCmd) } - it.NextOverlap() + it.NextOverlap(allCmd) } } @@ -983,7 +982,7 @@ func BenchmarkBTreeIterOverlapScan(b *testing.B) { it := tr.MakeIter() it.FirstOverlap(item) for it.Valid() { - it.NextOverlap() + it.NextOverlap(item) } } } diff --git a/pkg/util/interval/generic/internal/interval_btree_tmpl.go b/pkg/util/interval/generic/internal/interval_btree_tmpl.go index 8b1af8fa8a10..03ec78e04014 100644 --- a/pkg/util/interval/generic/internal/interval_btree_tmpl.go +++ b/pkg/util/interval/generic/internal/interval_btree_tmpl.go @@ -1038,8 +1038,6 @@ func (i *iterator) Cur() T { // It does so because the item at this position is the first item with a // start key larger than the search range's end key. type overlapScan struct { - item T // search item - // The "soft" lower-bound constraint. constrMinN *node constrMinPos int16 @@ -1058,29 +1056,24 @@ func (i *iterator) FirstOverlap(item T) { return } i.pos = 0 - i.o = overlapScan{item: item} - i.constrainMinSearchBounds() - i.constrainMaxSearchBounds() - i.findNextOverlap() + i.o = overlapScan{} + i.constrainMinSearchBounds(item) + i.constrainMaxSearchBounds(item) + i.findNextOverlap(item) } // NextOverlap positions the iterator to the item immediately following // its current position that overlaps with the search item. -func (i *iterator) NextOverlap() { +func (i *iterator) NextOverlap(item T) { if i.n == nil { return } - if i.o.item == nilT { - // Invalid. Mixed overlap scan with non-overlap scan. - i.pos = i.n.count - return - } i.pos++ - i.findNextOverlap() + i.findNextOverlap(item) } -func (i *iterator) constrainMinSearchBounds() { - k := i.o.item.Key() +func (i *iterator) constrainMinSearchBounds(item T) { + k := item.Key() j := sort.Search(int(i.n.count), func(j int) bool { return bytes.Compare(k, i.n.items[j].Key()) <= 0 }) @@ -1088,8 +1081,8 @@ func (i *iterator) constrainMinSearchBounds() { i.o.constrMinPos = int16(j) } -func (i *iterator) constrainMaxSearchBounds() { - up := upperBound(i.o.item) +func (i *iterator) constrainMaxSearchBounds(item T) { + up := upperBound(item) j := sort.Search(int(i.n.count), func(j int) bool { return !up.contains(i.n.items[j]) }) @@ -1097,24 +1090,24 @@ func (i *iterator) constrainMaxSearchBounds() { i.o.constrMaxPos = int16(j) } -func (i *iterator) findNextOverlap() { +func (i *iterator) findNextOverlap(item T) { for { if i.pos > i.n.count { // Iterate up tree. i.ascend() } else if !i.n.leaf { // Iterate down tree. - if i.o.constrMinReached || i.n.children[i.pos].max.contains(i.o.item) { + if i.o.constrMinReached || i.n.children[i.pos].max.contains(item) { par := i.n pos := i.pos i.descend(par, pos) // Refine the constraint bounds, if necessary. if par == i.o.constrMinN && pos == i.o.constrMinPos { - i.constrainMinSearchBounds() + i.constrainMinSearchBounds(item) } if par == i.o.constrMaxN && pos == i.o.constrMaxPos { - i.constrainMaxSearchBounds() + i.constrainMaxSearchBounds(item) } continue } @@ -1140,7 +1133,7 @@ func (i *iterator) findNextOverlap() { // span's start key. return } - if upperBound(i.n.items[i.pos]).contains(i.o.item) { + if upperBound(i.n.items[i.pos]).contains(item) { return } } diff --git a/pkg/util/interval/generic/internal/interval_btree_tmpl_test.go b/pkg/util/interval/generic/internal/interval_btree_tmpl_test.go index 4728e90da85f..8daf5cf97caf 100644 --- a/pkg/util/interval/generic/internal/interval_btree_tmpl_test.go +++ b/pkg/util/interval/generic/internal/interval_btree_tmpl_test.go @@ -224,7 +224,7 @@ func checkIter(t *testing.T, it iterator, start, end int, spanMemo map[int]roach } all := newItem(spanWithEnd(start, end)) - for it.FirstOverlap(all); it.Valid(); it.NextOverlap() { + for it.FirstOverlap(all); it.Valid(); it.NextOverlap(all) { item := it.Cur() expected := spanWithMemo(i, spanMemo) if !expected.Equal(spanFromItem(item)) { @@ -344,7 +344,8 @@ func TestBTreeSeekOverlap(t *testing.T) { // Iterate over overlaps with a point scan. it := tr.MakeIter() for i := 0; i < count+size; i++ { - it.FirstOverlap(newItem(spanWithEnd(i, i))) + scanItem := newItem(spanWithEnd(i, i)) + it.FirstOverlap(scanItem) for j := 0; j < size+1; j++ { expStart := i - size + j if expStart < 0 { @@ -363,7 +364,7 @@ func TestBTreeSeekOverlap(t *testing.T) { t.Fatalf("%d: expected %s, but found %s", i, expected, spanFromItem(item)) } - it.NextOverlap() + it.NextOverlap(scanItem) } if it.Valid() { t.Fatalf("%d: expected invalid iterator %v", i, it.Cur()) @@ -377,7 +378,8 @@ func TestBTreeSeekOverlap(t *testing.T) { // Iterate over overlaps with a range scan. it = tr.MakeIter() for i := 0; i < count+size; i++ { - it.FirstOverlap(newItem(spanWithEnd(i, i+size+1))) + scanItem := newItem(spanWithEnd(i, i+size+1)) + it.FirstOverlap(scanItem) for j := 0; j < 2*size+1; j++ { expStart := i - size + j if expStart < 0 { @@ -396,7 +398,7 @@ func TestBTreeSeekOverlap(t *testing.T) { t.Fatalf("%d: expected %s, but found %s", i, expected, spanFromItem(item)) } - it.NextOverlap() + it.NextOverlap(scanItem) } if it.Valid() { t.Fatalf("%d: expected invalid iterator %v", i, it.Cur()) @@ -436,14 +438,14 @@ func TestBTreeSeekOverlapRandom(t *testing.T) { const scanTrials = 100 for j := 0; j < scanTrials; j++ { - var scanLa T + var scanItem T scanStart := rng.Intn(count) scanEnd := rng.Intn(count + 10) if scanEnd <= scanStart { scanEnd = scanStart - scanLa = newItem(spanWithEnd(scanStart, scanEnd)) + scanItem = newItem(spanWithEnd(scanStart, scanEnd)) } else { - scanLa = newItem(spanWithEnd(scanStart, scanEnd+1)) + scanItem = newItem(spanWithEnd(scanStart, scanEnd+1)) } var exp, found []T @@ -454,13 +456,13 @@ func TestBTreeSeekOverlapRandom(t *testing.T) { } it := tr.MakeIter() - it.FirstOverlap(scanLa) + it.FirstOverlap(scanItem) for it.Valid() { found = append(found, it.Cur()) - it.NextOverlap() + it.NextOverlap(scanItem) } - require.Equal(t, len(exp), len(found), "search for %v", spanFromItem(scanLa)) + require.Equal(t, len(exp), len(found), "search for %v", spanFromItem(scanItem)) } } } @@ -796,6 +798,7 @@ func BenchmarkBTreeMakeIter(b *testing.B) { // BenchmarkBTreeIterSeekGE measures the cost of seeking a btree iterator // forward. func BenchmarkBTreeIterSeekGE(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -806,12 +809,10 @@ func BenchmarkBTreeIterSeekGE(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { s := spans[rng.Intn(len(spans))] + it := tr.MakeIter() it.SeekGE(newItem(s)) if testing.Verbose() { if !it.Valid() { @@ -828,6 +829,7 @@ func BenchmarkBTreeIterSeekGE(b *testing.B) { // BenchmarkBTreeIterSeekLT measures the cost of seeking a btree iterator // backward. func BenchmarkBTreeIterSeekLT(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -838,13 +840,11 @@ func BenchmarkBTreeIterSeekLT(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { j := rng.Intn(len(spans)) s := spans[j] + it := tr.MakeIter() it.SeekLT(newItem(s)) if testing.Verbose() { if j == 0 { @@ -868,6 +868,7 @@ func BenchmarkBTreeIterSeekLT(b *testing.B) { // BenchmarkBTreeIterFirstOverlap measures the cost of finding a single // overlapping item using a btree iterator. func BenchmarkBTreeIterFirstOverlap(b *testing.B) { + rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) forBenchmarkSizes(b, func(b *testing.B, count int) { var spans []roachpb.Span var tr btree @@ -878,13 +879,11 @@ func BenchmarkBTreeIterFirstOverlap(b *testing.B) { tr.Set(newItem(s)) } - rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - it := tr.MakeIter() - b.ResetTimer() for i := 0; i < b.N; i++ { j := rng.Intn(len(spans)) s := spans[j] + it := tr.MakeIter() it.FirstOverlap(newItem(s)) if testing.Verbose() { if !it.Valid() { @@ -961,7 +960,7 @@ func BenchmarkBTreeIterNextOverlap(b *testing.B) { if !it.Valid() { it.FirstOverlap(allCmd) } - it.NextOverlap() + it.NextOverlap(allCmd) } } @@ -983,7 +982,7 @@ func BenchmarkBTreeIterOverlapScan(b *testing.B) { it := tr.MakeIter() it.FirstOverlap(item) for it.Valid() { - it.NextOverlap() + it.NextOverlap(item) } } }