github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/level_iter_test.go (about) 1 // Copyright 2018 The LevelDB-Go and Pebble and Bitalostored Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package bitalostable 6 7 import ( 8 "bytes" 9 "fmt" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/require" 15 "github.com/zuoyebang/bitalostable/bloom" 16 "github.com/zuoyebang/bitalostable/internal/base" 17 "github.com/zuoyebang/bitalostable/internal/datadriven" 18 "github.com/zuoyebang/bitalostable/internal/keyspan" 19 "github.com/zuoyebang/bitalostable/internal/manifest" 20 "github.com/zuoyebang/bitalostable/internal/rangedel" 21 "github.com/zuoyebang/bitalostable/sstable" 22 "github.com/zuoyebang/bitalostable/vfs" 23 "golang.org/x/exp/rand" 24 ) 25 26 const ( 27 level = 1 28 ) 29 30 func TestLevelIter(t *testing.T) { 31 var iters []*fakeIter 32 var files manifest.LevelSlice 33 34 newIters := func( 35 file *manifest.FileMetadata, opts *IterOptions, _ internalIterOpts, 36 ) (internalIterator, keyspan.FragmentIterator, error) { 37 f := *iters[file.FileNum] 38 f.lower = opts.GetLowerBound() 39 f.upper = opts.GetUpperBound() 40 return &f, nil, nil 41 } 42 43 datadriven.RunTest(t, "testdata/level_iter", func(d *datadriven.TestData) string { 44 switch d.Cmd { 45 case "define": 46 iters = nil 47 var metas []*fileMetadata 48 for _, line := range strings.Split(d.Input, "\n") { 49 f := &fakeIter{} 50 for _, key := range strings.Fields(line) { 51 j := strings.Index(key, ":") 52 f.keys = append(f.keys, base.ParseInternalKey(key[:j])) 53 f.vals = append(f.vals, []byte(key[j+1:])) 54 } 55 iters = append(iters, f) 56 57 meta := (&fileMetadata{ 58 FileNum: FileNum(len(metas)), 59 }).ExtendPointKeyBounds( 60 DefaultComparer.Compare, 61 f.keys[0], 62 f.keys[len(f.keys)-1], 63 ) 64 metas = append(metas, meta) 65 } 66 files = manifest.NewLevelSliceKeySorted(base.DefaultComparer.Compare, metas) 67 68 return "" 69 70 case "iter": 71 var opts IterOptions 72 for _, arg := range d.CmdArgs { 73 if len(arg.Vals) != 1 { 74 return fmt.Sprintf("%s: %s=<value>", d.Cmd, arg.Key) 75 } 76 switch arg.Key { 77 case "lower": 78 opts.LowerBound = []byte(arg.Vals[0]) 79 case "upper": 80 opts.UpperBound = []byte(arg.Vals[0]) 81 default: 82 return fmt.Sprintf("%s: unknown arg: %s", d.Cmd, arg.Key) 83 } 84 } 85 86 iter := newLevelIter(opts, DefaultComparer.Compare, 87 func(a []byte) int { return len(a) }, newIters, files.Iter(), manifest.Level(level), 88 nil) 89 defer iter.Close() 90 // Fake up the range deletion initialization. 91 iter.initRangeDel(new(keyspan.FragmentIterator)) 92 iter.disableInvariants = true 93 return runInternalIterCmd(d, iter, iterCmdVerboseKey) 94 95 case "load": 96 // The "load" command allows testing the iterator options passed to load 97 // sstables. 98 // 99 // load <key> [lower=<key>] [upper=<key>] 100 var opts IterOptions 101 var key string 102 for _, arg := range d.CmdArgs { 103 if len(arg.Vals) == 0 { 104 key = arg.Key 105 continue 106 } 107 if len(arg.Vals) != 1 { 108 return fmt.Sprintf("%s: %s=<value>", d.Cmd, arg.Key) 109 } 110 switch arg.Key { 111 case "lower": 112 opts.LowerBound = []byte(arg.Vals[0]) 113 case "upper": 114 opts.UpperBound = []byte(arg.Vals[0]) 115 default: 116 return fmt.Sprintf("%s: unknown arg: %s", d.Cmd, arg.Key) 117 } 118 } 119 120 var tableOpts *IterOptions 121 newIters2 := func( 122 file *manifest.FileMetadata, opts *IterOptions, internalOpts internalIterOpts, 123 ) (internalIterator, keyspan.FragmentIterator, error) { 124 tableOpts = opts 125 return newIters(file, opts, internalOpts) 126 } 127 128 iter := newLevelIter(opts, DefaultComparer.Compare, 129 func(a []byte) int { return len(a) }, newIters2, files.Iter(), 130 manifest.Level(level), nil) 131 iter.SeekGE([]byte(key), base.SeekGEFlagsNone) 132 lower, upper := tableOpts.GetLowerBound(), tableOpts.GetUpperBound() 133 return fmt.Sprintf("[%s,%s]\n", lower, upper) 134 135 default: 136 return fmt.Sprintf("unknown command: %s", d.Cmd) 137 } 138 }) 139 } 140 141 type levelIterTest struct { 142 cmp base.Comparer 143 mem vfs.FS 144 readers []*sstable.Reader 145 metas []*fileMetadata 146 itersCreated int 147 } 148 149 func newLevelIterTest() *levelIterTest { 150 lt := &levelIterTest{ 151 cmp: *DefaultComparer, 152 mem: vfs.NewMem(), 153 } 154 lt.cmp.Split = func(a []byte) int { return len(a) } 155 return lt 156 } 157 158 func (lt *levelIterTest) newIters( 159 file *manifest.FileMetadata, opts *IterOptions, iio internalIterOpts, 160 ) (internalIterator, keyspan.FragmentIterator, error) { 161 lt.itersCreated++ 162 iter, err := lt.readers[file.FileNum].NewIterWithBlockPropertyFilters(opts.LowerBound, opts.UpperBound, nil, true, iio.stats) 163 if err != nil { 164 return nil, nil, err 165 } 166 rangeDelIter, err := lt.readers[file.FileNum].NewRawRangeDelIter() 167 if err != nil { 168 return nil, nil, err 169 } 170 return iter, rangeDelIter, nil 171 } 172 173 func (lt *levelIterTest) runClear(d *datadriven.TestData) string { 174 lt.mem = vfs.NewMem() 175 for _, r := range lt.readers { 176 r.Close() 177 } 178 lt.readers = nil 179 lt.metas = nil 180 lt.itersCreated = 0 181 return "" 182 } 183 184 func (lt *levelIterTest) runBuild(d *datadriven.TestData) string { 185 fileNum := FileNum(len(lt.readers)) 186 name := fmt.Sprint(fileNum) 187 f0, err := lt.mem.Create(name) 188 if err != nil { 189 return err.Error() 190 } 191 192 tableFormat := sstable.TableFormatRocksDBv2 193 for _, arg := range d.CmdArgs { 194 if arg.Key == "format" { 195 switch arg.Vals[0] { 196 case "rocksdbv2": 197 tableFormat = sstable.TableFormatRocksDBv2 198 case "bitalostablev2": 199 tableFormat = sstable.TableFormatPebblev2 200 } 201 } 202 } 203 fp := bloom.FilterPolicy(10) 204 w := sstable.NewWriter(f0, sstable.WriterOptions{ 205 Comparer: <.cmp, 206 FilterPolicy: fp, 207 TableFormat: tableFormat, 208 }) 209 var tombstones []keyspan.Span 210 f := keyspan.Fragmenter{ 211 Cmp: lt.cmp.Compare, 212 Format: lt.cmp.FormatKey, 213 Emit: func(fragmented keyspan.Span) { 214 tombstones = append(tombstones, fragmented) 215 }, 216 } 217 for _, key := range strings.Split(d.Input, "\n") { 218 j := strings.Index(key, ":") 219 ikey := base.ParseInternalKey(key[:j]) 220 value := []byte(key[j+1:]) 221 switch ikey.Kind() { 222 case InternalKeyKindRangeDelete: 223 f.Add(rangedel.Decode(ikey, value, nil)) 224 case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete: 225 if err := w.AddRangeKey(ikey, value); err != nil { 226 return err.Error() 227 } 228 default: 229 if err := w.Add(ikey, value); err != nil { 230 return err.Error() 231 } 232 } 233 } 234 f.Finish() 235 for _, v := range tombstones { 236 if err := rangedel.Encode(&v, w.Add); err != nil { 237 return err.Error() 238 } 239 } 240 if err := w.Close(); err != nil { 241 return err.Error() 242 } 243 meta, err := w.Metadata() 244 if err != nil { 245 return err.Error() 246 } 247 248 f1, err := lt.mem.Open(name) 249 if err != nil { 250 return err.Error() 251 } 252 r, err := sstable.NewReader(f1, sstable.ReaderOptions{ 253 Filters: map[string]FilterPolicy{ 254 fp.Name(): fp, 255 }, 256 }) 257 if err != nil { 258 return err.Error() 259 } 260 lt.readers = append(lt.readers, r) 261 m := &fileMetadata{FileNum: fileNum} 262 if meta.HasPointKeys { 263 m.ExtendPointKeyBounds(lt.cmp.Compare, meta.SmallestPoint, meta.LargestPoint) 264 } 265 if meta.HasRangeDelKeys { 266 m.ExtendPointKeyBounds(lt.cmp.Compare, meta.SmallestRangeDel, meta.LargestRangeDel) 267 } 268 if meta.HasRangeKeys { 269 m.ExtendRangeKeyBounds(lt.cmp.Compare, meta.SmallestRangeKey, meta.LargestRangeKey) 270 } 271 lt.metas = append(lt.metas, m) 272 273 var buf bytes.Buffer 274 for _, f := range lt.metas { 275 fmt.Fprintf(&buf, "%d: %s-%s\n", f.FileNum, f.Smallest, f.Largest) 276 } 277 return buf.String() 278 } 279 280 func TestLevelIterBoundaries(t *testing.T) { 281 lt := newLevelIterTest() 282 defer lt.runClear(nil) 283 284 var iter *levelIter 285 datadriven.RunTest(t, "testdata/level_iter_boundaries", func(d *datadriven.TestData) string { 286 switch d.Cmd { 287 case "clear": 288 return lt.runClear(d) 289 290 case "build": 291 return lt.runBuild(d) 292 293 case "iter": 294 // The save and continue parameters allow us to save the iterator 295 // for later continued use. 296 save := false 297 cont := false 298 for _, arg := range d.CmdArgs { 299 switch arg.Key { 300 case "save": 301 save = true 302 case "continue": 303 cont = true 304 default: 305 return fmt.Sprintf("%s: unknown arg: %s", d.Cmd, arg.Key) 306 } 307 } 308 if !cont && iter != nil { 309 return "preceding iter was not closed" 310 } 311 if cont && iter == nil { 312 return "no existing iter" 313 } 314 if iter == nil { 315 slice := manifest.NewLevelSliceKeySorted(lt.cmp.Compare, lt.metas) 316 iter = newLevelIter(IterOptions{}, DefaultComparer.Compare, 317 func(a []byte) int { return len(a) }, lt.newIters, slice.Iter(), 318 manifest.Level(level), nil) 319 // Fake up the range deletion initialization. 320 iter.initRangeDel(new(keyspan.FragmentIterator)) 321 } 322 if !save { 323 defer func() { 324 iter.Close() 325 iter = nil 326 }() 327 } 328 return runInternalIterCmd(d, iter, iterCmdVerboseKey) 329 330 case "file-pos": 331 // Returns the FileNum at which the iterator is positioned. 332 if iter == nil { 333 return "nil levelIter" 334 } 335 if iter.iterFile == nil { 336 return "nil iterFile" 337 } 338 return fmt.Sprintf("file %d", iter.iterFile.FileNum) 339 340 default: 341 return fmt.Sprintf("unknown command: %s", d.Cmd) 342 } 343 }) 344 } 345 346 // levelIterTestIter allows a datadriven test to use runInternalIterCmd and 347 // perform parallel operations on both both a levelIter and rangeDelIter. 348 type levelIterTestIter struct { 349 *levelIter 350 rangeDelIter keyspan.FragmentIterator 351 } 352 353 func (i *levelIterTestIter) rangeDelSeek( 354 key []byte, ikey *InternalKey, val []byte, dir int, 355 ) (*InternalKey, []byte) { 356 var tombstone keyspan.Span 357 if i.rangeDelIter != nil { 358 var t *keyspan.Span 359 if dir < 0 { 360 t = keyspan.SeekLE(i.levelIter.cmp, i.rangeDelIter, key) 361 } else { 362 t = keyspan.SeekGE(i.levelIter.cmp, i.rangeDelIter, key) 363 } 364 if t != nil { 365 tombstone = t.Visible(1000) 366 } 367 } 368 if ikey == nil { 369 return &InternalKey{ 370 UserKey: []byte(fmt.Sprintf("./%s", tombstone)), 371 }, nil 372 } 373 return &InternalKey{ 374 UserKey: []byte(fmt.Sprintf("%s/%s", ikey.UserKey, tombstone)), 375 Trailer: ikey.Trailer, 376 }, val 377 } 378 379 func (i *levelIterTestIter) String() string { 380 return "level-iter-test" 381 } 382 383 func (i *levelIterTestIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, []byte) { 384 ikey, val := i.levelIter.SeekGE(key, flags) 385 return i.rangeDelSeek(key, ikey, val, 1) 386 } 387 388 func (i *levelIterTestIter) SeekPrefixGE( 389 prefix, key []byte, flags base.SeekGEFlags, 390 ) (*base.InternalKey, []byte) { 391 ikey, val := i.levelIter.SeekPrefixGE(prefix, key, flags) 392 return i.rangeDelSeek(key, ikey, val, 1) 393 } 394 395 func (i *levelIterTestIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, []byte) { 396 ikey, val := i.levelIter.SeekLT(key, flags) 397 return i.rangeDelSeek(key, ikey, val, -1) 398 } 399 400 func TestLevelIterSeek(t *testing.T) { 401 lt := newLevelIterTest() 402 defer lt.runClear(nil) 403 404 datadriven.RunTest(t, "testdata/level_iter_seek", func(d *datadriven.TestData) string { 405 switch d.Cmd { 406 case "clear": 407 return lt.runClear(d) 408 409 case "build": 410 return lt.runBuild(d) 411 412 case "iter": 413 var stats base.InternalIteratorStats 414 slice := manifest.NewLevelSliceKeySorted(lt.cmp.Compare, lt.metas) 415 iter := &levelIterTestIter{levelIter: &levelIter{}} 416 iter.init(IterOptions{}, DefaultComparer.Compare, 417 func(a []byte) int { return len(a) }, lt.newIters, slice.Iter(), 418 manifest.Level(level), internalIterOpts{stats: &stats}) 419 defer iter.Close() 420 iter.initRangeDel(&iter.rangeDelIter) 421 return runInternalIterCmd(d, iter, iterCmdVerboseKey, iterCmdStats(&stats)) 422 423 case "iters-created": 424 return fmt.Sprintf("%d", lt.itersCreated) 425 default: 426 return fmt.Sprintf("unknown command: %s", d.Cmd) 427 } 428 }) 429 } 430 431 func buildLevelIterTables( 432 b *testing.B, blockSize, restartInterval, count int, 433 ) ([]*sstable.Reader, manifest.LevelSlice, [][]byte, func()) { 434 mem := vfs.NewMem() 435 files := make([]vfs.File, count) 436 for i := range files { 437 f, err := mem.Create(fmt.Sprintf("bench%d", i)) 438 if err != nil { 439 b.Fatal(err) 440 } 441 files[i] = f 442 } 443 444 writers := make([]*sstable.Writer, len(files)) 445 for i := range files { 446 writers[i] = sstable.NewWriter(files[i], sstable.WriterOptions{ 447 BlockRestartInterval: restartInterval, 448 BlockSize: blockSize, 449 Compression: NoCompression, 450 }) 451 } 452 453 var keys [][]byte 454 var i int 455 const targetSize = 2 << 20 456 for _, w := range writers { 457 for ; w.EstimatedSize() < targetSize; i++ { 458 key := []byte(fmt.Sprintf("%08d", i)) 459 keys = append(keys, key) 460 ikey := base.MakeInternalKey(key, 0, InternalKeyKindSet) 461 w.Add(ikey, nil) 462 } 463 if err := w.Close(); err != nil { 464 b.Fatal(err) 465 } 466 } 467 468 opts := sstable.ReaderOptions{Cache: NewCache(128 << 20)} 469 defer opts.Cache.Unref() 470 readers := make([]*sstable.Reader, len(files)) 471 for i := range files { 472 f, err := mem.Open(fmt.Sprintf("bench%d", i)) 473 if err != nil { 474 b.Fatal(err) 475 } 476 readers[i], err = sstable.NewReader(f, opts) 477 if err != nil { 478 b.Fatal(err) 479 } 480 } 481 482 cleanup := func() { 483 for _, r := range readers { 484 require.NoError(b, r.Close()) 485 } 486 } 487 488 meta := make([]*fileMetadata, len(readers)) 489 for i := range readers { 490 iter, err := readers[i].NewIter(nil /* lower */, nil /* upper */) 491 require.NoError(b, err) 492 smallest, _ := iter.First() 493 meta[i] = &fileMetadata{} 494 meta[i].FileNum = FileNum(i) 495 largest, _ := iter.Last() 496 meta[i].ExtendPointKeyBounds(opts.Comparer.Compare, (*smallest).Clone(), (*largest).Clone()) 497 } 498 slice := manifest.NewLevelSliceKeySorted(base.DefaultComparer.Compare, meta) 499 return readers, slice, keys, cleanup 500 } 501 502 func BenchmarkLevelIterSeekGE(b *testing.B) { 503 const blockSize = 32 << 10 504 505 for _, restartInterval := range []int{16} { 506 b.Run(fmt.Sprintf("restart=%d", restartInterval), 507 func(b *testing.B) { 508 for _, count := range []int{5} { 509 b.Run(fmt.Sprintf("count=%d", count), 510 func(b *testing.B) { 511 readers, metas, keys, cleanup := buildLevelIterTables(b, blockSize, restartInterval, count) 512 defer cleanup() 513 newIters := func( 514 file *manifest.FileMetadata, _ *IterOptions, _ internalIterOpts, 515 ) (internalIterator, keyspan.FragmentIterator, error) { 516 iter, err := readers[file.FileNum].NewIter(nil /* lower */, nil /* upper */) 517 return iter, nil, err 518 } 519 l := newLevelIter(IterOptions{}, DefaultComparer.Compare, nil, newIters, metas.Iter(), manifest.Level(level), nil) 520 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 521 522 b.ResetTimer() 523 for i := 0; i < b.N; i++ { 524 l.SeekGE(keys[rng.Intn(len(keys))], base.SeekGEFlagsNone) 525 } 526 l.Close() 527 }) 528 } 529 }) 530 } 531 } 532 533 // A benchmark that simulates the behavior of a levelIter being used as part 534 // of a mergingIter where narrow bounds are repeatedly set and used to Seek 535 // and then iterate over the keys within the bounds. This resembles MVCC 536 // scanning by CockroachDB when doing a lookup/index join with a large number 537 // of left rows, that are batched and reuse the same iterator, and which can 538 // have good locality of access. This results in the successive bounds being 539 // in the same file. 540 func BenchmarkLevelIterSeqSeekGEWithBounds(b *testing.B) { 541 const blockSize = 32 << 10 542 543 for _, restartInterval := range []int{16} { 544 b.Run(fmt.Sprintf("restart=%d", restartInterval), 545 func(b *testing.B) { 546 for _, count := range []int{5} { 547 b.Run(fmt.Sprintf("count=%d", count), 548 func(b *testing.B) { 549 readers, metas, keys, cleanup := 550 buildLevelIterTables(b, blockSize, restartInterval, count) 551 defer cleanup() 552 // This newIters is cheaper than in practice since it does not do 553 // tableCacheShard.findNode. 554 newIters := func( 555 file *manifest.FileMetadata, opts *IterOptions, _ internalIterOpts, 556 ) (internalIterator, keyspan.FragmentIterator, error) { 557 iter, err := readers[file.FileNum].NewIter( 558 opts.LowerBound, opts.UpperBound) 559 return iter, nil, err 560 } 561 l := newLevelIter(IterOptions{}, DefaultComparer.Compare, nil, newIters, metas.Iter(), manifest.Level(level), nil) 562 // Fake up the range deletion initialization, to resemble the usage 563 // in a mergingIter. 564 l.initRangeDel(new(keyspan.FragmentIterator)) 565 keyCount := len(keys) 566 b.ResetTimer() 567 for i := 0; i < b.N; i++ { 568 pos := i % (keyCount - 1) 569 l.SetBounds(keys[pos], keys[pos+1]) 570 // SeekGE will return keys[pos]. 571 k, _ := l.SeekGE(keys[pos], base.SeekGEFlagsNone) 572 // Next() will get called once and return nil. 573 for k != nil { 574 k, _ = l.Next() 575 } 576 } 577 l.Close() 578 }) 579 } 580 }) 581 } 582 } 583 584 // BenchmarkLevelIterSeqSeekPrefixGE simulates the behavior of a levelIter 585 // being used as part of a mergingIter where SeekPrefixGE is used to seek in a 586 // monotonically increasing manner. This resembles key-value lookups done by 587 // CockroachDB when evaluating Put operations. 588 func BenchmarkLevelIterSeqSeekPrefixGE(b *testing.B) { 589 const blockSize = 32 << 10 590 const restartInterval = 16 591 readers, metas, keys, cleanup := 592 buildLevelIterTables(b, blockSize, restartInterval, 5) 593 defer cleanup() 594 // This newIters is cheaper than in practice since it does not do 595 // tableCacheShard.findNode. 596 newIters := func( 597 file *manifest.FileMetadata, opts *IterOptions, _ internalIterOpts, 598 ) (internalIterator, keyspan.FragmentIterator, error) { 599 iter, err := readers[file.FileNum].NewIter( 600 opts.LowerBound, opts.UpperBound) 601 return iter, nil, err 602 } 603 604 for _, skip := range []int{1, 2, 4, 8, 16} { 605 for _, useNext := range []bool{false, true} { 606 b.Run(fmt.Sprintf("skip=%d/use-next=%t", skip, useNext), 607 func(b *testing.B) { 608 l := newLevelIter(IterOptions{}, DefaultComparer.Compare, 609 func(a []byte) int { return len(a) }, newIters, metas.Iter(), 610 manifest.Level(level), nil) 611 // Fake up the range deletion initialization, to resemble the usage 612 // in a mergingIter. 613 l.initRangeDel(new(keyspan.FragmentIterator)) 614 keyCount := len(keys) 615 pos := 0 616 l.SeekPrefixGE(keys[pos], keys[pos], base.SeekGEFlagsNone) 617 b.ResetTimer() 618 for i := 0; i < b.N; i++ { 619 pos += skip 620 var flags base.SeekGEFlags 621 if useNext { 622 flags = flags.EnableTrySeekUsingNext() 623 } 624 if pos >= keyCount { 625 pos = 0 626 flags = flags.DisableTrySeekUsingNext() 627 } 628 // SeekPrefixGE will return keys[pos]. 629 l.SeekPrefixGE(keys[pos], keys[pos], flags) 630 } 631 b.StopTimer() 632 l.Close() 633 }) 634 } 635 } 636 } 637 638 func BenchmarkLevelIterNext(b *testing.B) { 639 const blockSize = 32 << 10 640 641 for _, restartInterval := range []int{16} { 642 b.Run(fmt.Sprintf("restart=%d", restartInterval), 643 func(b *testing.B) { 644 for _, count := range []int{5} { 645 b.Run(fmt.Sprintf("count=%d", count), 646 func(b *testing.B) { 647 readers, metas, _, cleanup := buildLevelIterTables(b, blockSize, restartInterval, count) 648 defer cleanup() 649 newIters := func( 650 file *manifest.FileMetadata, _ *IterOptions, _ internalIterOpts, 651 ) (internalIterator, keyspan.FragmentIterator, error) { 652 iter, err := readers[file.FileNum].NewIter(nil /* lower */, nil /* upper */) 653 return iter, nil, err 654 } 655 l := newLevelIter(IterOptions{}, DefaultComparer.Compare, nil, newIters, metas.Iter(), manifest.Level(level), nil) 656 657 b.ResetTimer() 658 for i := 0; i < b.N; i++ { 659 key, _ := l.Next() 660 if key == nil { 661 key, _ = l.First() 662 } 663 _ = key 664 } 665 l.Close() 666 }) 667 } 668 }) 669 } 670 } 671 672 func BenchmarkLevelIterPrev(b *testing.B) { 673 const blockSize = 32 << 10 674 675 for _, restartInterval := range []int{16} { 676 b.Run(fmt.Sprintf("restart=%d", restartInterval), 677 func(b *testing.B) { 678 for _, count := range []int{5} { 679 b.Run(fmt.Sprintf("count=%d", count), 680 func(b *testing.B) { 681 readers, metas, _, cleanup := buildLevelIterTables(b, blockSize, restartInterval, count) 682 defer cleanup() 683 newIters := func( 684 file *manifest.FileMetadata, _ *IterOptions, _ internalIterOpts, 685 ) (internalIterator, keyspan.FragmentIterator, error) { 686 iter, err := readers[file.FileNum].NewIter(nil /* lower */, nil /* upper */) 687 return iter, nil, err 688 } 689 l := newLevelIter(IterOptions{}, DefaultComparer.Compare, nil, newIters, metas.Iter(), manifest.Level(level), nil) 690 691 b.ResetTimer() 692 for i := 0; i < b.N; i++ { 693 key, _ := l.Prev() 694 if key == nil { 695 key, _ = l.Last() 696 } 697 _ = key 698 } 699 l.Close() 700 }) 701 } 702 }) 703 } 704 }