diff --git a/roaring/btree.go b/roaring/btree.go index caa0bb46e..76427059f 100644 --- a/roaring/btree.go +++ b/roaring/btree.go @@ -38,9 +38,9 @@ import ( const ( // kx must be >= 2 - kx = 128 //TODO benchmark tune this number if using custom key/value type(s). + kx = 126 //TODO benchmark tune this number if using custom key/value type(s). // kd must be >= 1 - kd = 128 //TODO benchmark tune this number if using custom key/value type(s). + kd = 254 //TODO benchmark tune this number if using custom key/value type(s). ) var ( @@ -52,9 +52,8 @@ var ( type btTpool struct{ sync.Pool } -func (p *btTpool) get(cmp Cmp) *tree { +func (p *btTpool) get() *tree { x := p.Get().(*tree) - x.cmp = cmp return x } @@ -67,19 +66,12 @@ func (p *btEpool) get(err error, hit bool, i int, k uint64, q *d, t *tree, ver i } type ( - // Cmp compares a and b. Return value is: - // - // < 0 if a < b - // 0 if a == b - // > 0 if a > b - // - Cmp func(a, b uint64) int64 - d struct { // data page - c int - d [2*kd + 1]de - n *d - p *d + dTree //lint:ignore U1000 this is conditional on a build flag + c int + d [2*kd + 1]de + n *d + p *d } de struct { // d element @@ -107,12 +99,12 @@ type ( // tree is a B+tree. tree struct { - c int - cmp Cmp - first *d - last *d - r interface{} - ver int64 + treeInst //lint:ignore U1000 this is conditional on a build flag + c int + first *d + last *d + r interface{} + ver int64 } xe struct { // x element @@ -197,6 +189,7 @@ func (q *x) siblings(i int) (l, r *d) { // -------------------------------------------------------------------------- d func (l *d) mvL(r *d, c int) { + r.didCopy(r.c) copy(l.d[l.c:], r.d[:c]) copy(r.d[:], r.d[c:r.c]) // Zero out the de's here to prevent reading bad data @@ -209,6 +202,7 @@ func (l *d) mvL(r *d, c int) { } func (l *d) mvR(r *d, c int) { + l.didCopy(r.c + c) copy(r.d[c:], r.d[:r.c]) copy(r.d[:c], l.d[l.c-c:]) // Zero out the de's here to prevent reading bad data @@ -224,8 +218,8 @@ func (l *d) mvR(r *d, c int) { // treeNew returns a newly created, empty Tree. The compare function is used // for key collation. -func treeNew(cmp Cmp) *tree { - return btTPool.get(cmp) +func treeNew() *tree { + return btTPool.get() } // Clear removes all K/V pairs from the tree. @@ -364,6 +358,7 @@ func (t *tree) extract(q *d, i int) { // (r *container) { //r = q.d[i].v // prepared for Extract q.c-- if i < q.c { + t.didCopy(q.c - i) copy(q.d[i:], q.d[i+1:q.c+1]) } q.d[q.c] = zde // GC @@ -379,13 +374,13 @@ func (t *tree) find(q interface{}, k uint64) (i int, ok bool) { for l <= h { m := (l + h) >> 1 mk = x.x[m].k - switch cmp := t.cmp(k, mk); { - case cmp > 0: + switch { + case k > mk: l = m + 1 - case cmp == 0: - return m, true - default: + case k < mk: h = m - 1 + default: + return m, true } } case *d: @@ -393,13 +388,13 @@ func (t *tree) find(q interface{}, k uint64) (i int, ok bool) { for l <= h { m := (l + h) >> 1 mk = x.d[m].k - switch cmp := t.cmp(k, mk); { - case cmp > 0: + switch { + case k > mk: l = m + 1 - case cmp == 0: - return m, true - default: + case k < mk: h = m - 1 + default: + return m, true } } } @@ -446,8 +441,10 @@ func (t *tree) Get(k uint64) (v *Container, ok bool) { func (t *tree) insert(q *d, i int, k uint64, v *Container) *d { t.ver++ + q.setTree(t) c := q.c if i < c { + t.didCopy(c - i) copy(q.d[i+1:], q.d[i:c]) } c++ @@ -704,6 +701,7 @@ func (t *tree) Put(k uint64, upd func(oldV *Container, exists bool) (newV *Conta func (t *tree) split(p *x, q *d, pi, i int, k uint64, v *Container) { t.ver++ r := btDPool.Get().(*d) + r.setTree(t) if q.n != nil { r.n = q.n r.n.p = r @@ -713,6 +711,7 @@ func (t *tree) split(p *x, q *d, pi, i int, k uint64, v *Container) { q.n = r r.p = q + t.didCopy(kd) copy(r.d[:], q.d[kd:2*kd]) for i := range q.d[kd:] { q.d[kd+i] = zde diff --git a/roaring/btree_test.go b/roaring/btree_test.go new file mode 100644 index 000000000..67fd49e68 --- /dev/null +++ b/roaring/btree_test.go @@ -0,0 +1,1490 @@ +// Copyright 2014 The b Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package roaring + +import ( + "bytes" + "fmt" + "io" + "math" + "math/rand" + "path" + "runtime" + "runtime/debug" + "strings" + "testing" + + "modernc.org/mathutil" + "modernc.org/strutil" +) + +var caller = func(s string, va ...interface{}) { + _, fn, fl, _ := runtime.Caller(2) + fmt.Printf("%s:%d: ", path.Base(fn), fl) + fmt.Printf(s, va...) + fmt.Println() +} + +func dbg(s string, va ...interface{}) { + if s == "" { + s = strings.Repeat("%v ", len(va)) + } + _, fn, fl, _ := runtime.Caller(1) + fmt.Printf("%s:%d: ", path.Base(fn), fl) + fmt.Printf(s, va...) + fmt.Println() +} + +func TODO(...interface{}) string { //TODOOK + _, fn, fl, _ := runtime.Caller(1) + return fmt.Sprintf("TODO: %s:%d:\n", path.Base(fn), fl) //TODOOK +} + +func use(...interface{}) {} + +func init() { // nolint: gochecknoinits + use(caller, dbg, TODO, isNil, (*tree).dump) //TODOOK +} + +// ============================================================================ + +func isNil(p interface{}) bool { + switch x := p.(type) { + case *x: + if x == nil { + return true + } + case *d: + if x == nil { + return true + } + } + return false +} + +func (t *tree) dump() string { + var buf bytes.Buffer + f := strutil.IndentFormatter(&buf, "\t") + + num := map[interface{}]int{} + visited := map[interface{}]bool{} + + handle := func(p interface{}) int { + if isNil(p) { + return 0 + } + + if n, ok := num[p]; ok { + return n + } + + n := len(num) + 1 + num[p] = n + return n + } + + var pagedump func(interface{}, string) + pagedump = func(p interface{}, pref string) { + if isNil(p) || visited[p] { + return + } + + visited[p] = true + switch x := p.(type) { + case *x: + h := handle(p) + n := 0 + for i, v := range x.x { + if v.ch != nil || v.k != 0 { + n = i + 1 + } + } + f.Format("%sX#%d(%p) n %d:%d {", pref, h, x, x.c, n) + a := []interface{}{} + for i, v := range x.x[:n] { + a = append(a, v.ch) + if i != 0 { + f.Format(" ") + } + f.Format("(C#%d K %v)", handle(v.ch), v.k) + } + f.Format("}\n") + for _, p := range a { + pagedump(p, pref+". ") + } + case *d: + h := handle(p) + n := 0 + for i, d := range x.d { + if d.v != nil { + n = i + 1 + } + } + f.Format("%sD#%d(%p) P#%d N#%d n %d:%d {", pref, h, x, handle(x.p), handle(x.n), x.c, n) + for i, d := range x.d[:n] { + if i != 0 { + f.Format(" ") + } + f.Format("%v:%v", d.k, d.v) + } + f.Format("}\n") + } + } + + pagedump(t.r, "") + s := buf.String() + if s != "" { + s = s[:len(s)-1] + } + return s +} + +func rng() *mathutil.FC32 { + x, err := mathutil.NewFC32(math.MinInt32/4, math.MaxInt32/4, false) + if err != nil { + panic(err) + } + + return x +} + +var dummyC [1000]Container + +func getDummyC(i int) *Container { + if i < 0 { + i *= -1 + } + return &dummyC[i%len(dummyC)] +} + +func copyBenchmark(fn func(int64) uint64) func(b *testing.B) { + return func(b *testing.B) { + r := treeNew() + for i := int64(0); i < int64(b.N); i++ { + j := fn(i) + r.Set(j, getDummyC(0)) + } + b.Logf("copied %d de for N=%d\n", r.countCopies(), b.N) + } +} + +func BenchmarkBtreeDeCopies(b *testing.B) { + b.Run("Linear", copyBenchmark(func(i int64) uint64 { return uint64(i) })) + b.Run("Random", copyBenchmark(func(i int64) uint64 { return rand.Uint64() })) +} + +func TestBtreeGet0(t *testing.T) { + r := treeNew() + if g, e := r.Len(), 0; g != e { + t.Fatal(g, e) + } + + _, ok := r.Get(42) + if ok { + t.Fatal(ok) + } + +} + +func TestBtreeSetGet0(t *testing.T) { + r := treeNew() + set := r.Set + set(42, getDummyC(0)) + if g, e := r.Len(), 1; g != e { + t.Fatal(g, e) + } + + v, ok := r.Get(42) + if !ok { + t.Fatal(ok) + } + + if g, e := v, getDummyC(0); g != e { + t.Fatal(g, e) + } + + set(42, getDummyC(1)) + if g, e := r.Len(), 1; g != e { + t.Fatal(g, e) + } + + v, ok = r.Get(42) + if !ok { + t.Fatal(ok) + } + + if g, e := v, getDummyC(1); g != e { + t.Fatal(g, e) + } + + set(420, getDummyC(2)) + if g, e := r.Len(), 2; g != e { + t.Fatal(g, e) + } + + v, ok = r.Get(42) + if !ok { + t.Fatal(ok) + } + + if g, e := v, getDummyC(1); g != e { + t.Fatal(g, e) + } + + v, ok = r.Get(420) + if !ok { + t.Fatal(ok) + } + + if g, e := v, getDummyC(2); g != e { + t.Fatal(g, e) + } +} + +func TestBtreeSetGet1(t *testing.T) { + const N = 40000 + for _, x := range []int{0, -1, 0x555555, 0xaaaaaa, 0x333333, 0xcccccc, 0x314159} { + r := treeNew() + set := r.Set + a := make([]int, N) + for i := range a { + a[i] = (i ^ x) << 1 + } + for i, k := range a { + set(uint64(k), getDummyC((k^x)%10)) + if g, e := r.Len(), i+1; g != e { + t.Fatal(i, g, e) + } + } + + for i, k := range a { + v, ok := r.Get(uint64(k)) + if !ok { + t.Fatal(i, k, v, ok) + } + + if g, e := v, getDummyC((k^x)%10); g != e { + t.Fatal(i, g, e) + } + + k |= 1 + _, ok = r.Get(uint64(k)) + if ok { + t.Fatal(i, k) + } + + } + + for _, k := range a { + r.Set(uint64(k), getDummyC((k^x)+42)) + } + + for i, k := range a { + v, ok := r.Get(uint64(k)) + if !ok { + t.Fatal(i, k, v, ok) + } + + if g, e := v, getDummyC((k^x)+42); g != e { + t.Fatal(i, g, e) + } + + k |= 1 + _, ok = r.Get(uint64(k)) + if ok { + t.Fatal(i, k) + } + } + } +} + +func TestBtreePrealloc(*testing.T) { + const n = 2e6 + rng := rng() + a := make([]int, n) + for i := range a { + a[i] = rng.Next() + } + r := treeNew() + for _, v := range a { + r.Set(uint64(v), nil) + } + r.Close() +} + +func TestBtreeSplitXOnEdge(t *testing.T) { + // verify how splitX works when splitting X for k pointing directly at split edge + tr := treeNew() + + set := tr.Set + // one index page with 2*kx+2 elements (last has .k=∞ so x.c=2*kx+1) + // which will splitX on next Set + for i := 0; i <= (2*kx+1)*2*kd; i++ { + // odd keys are left to be filled in second test + set(uint64(2*i), getDummyC(2*i)) + } + + x0 := tr.r.(*x) + if x0.c != 2*kx+1 { + t.Fatalf("x0.c: %v ; expected %v", x0.c, 2*kx+1) + } + + // set element with k directly at x0[kx].k + kedge := uint64(2 * (kx + 1) * (2 * kd)) + if x0.x[kx].k != kedge { + t.Fatalf("edge key before splitX: %v ; expected %v", x0.x[kx].k, kedge) + } + set(uint64(kedge), getDummyC(777)) + + // if splitX was wrong kedge:777 would land into wrong place with Get failing + v, ok := tr.Get(kedge) + if !(v == getDummyC(777) && ok) { + t.Fatalf("after splitX: Get(%v) -> %v, %v ; expected 777, true", kedge, v, ok) + } + + // now check the same when splitted X has parent + xr := tr.r.(*x) + if xr.c != 1 { // second x comes with k=∞ with .c index + t.Fatalf("after splitX: xr.c: %v ; expected 1", xr.c) + } + + if xr.x[0].ch != x0 { + t.Fatal("xr[0].ch is not x0") + } + + for i := 0; i <= (2*kx)*kd; i++ { + set(uint64(2*i+1), getDummyC(2*i+1)) + } + + // check x0 is in pre-splitX condition and still at the right place + if x0.c != 2*kx+1 { + t.Fatalf("x0.c: %v ; expected %v", x0.c, 2*kx+1) + } + if xr.x[0].ch != x0 { + t.Fatal("xr[0].ch is not x0") + } + + // set element with k directly at x0[kx].k + kedge = (kx + 1) * (2 * kd) + if x0.x[kx].k != kedge { + t.Fatalf("edge key before splitX: %v ; expected %v", x0.x[kx].k, kedge) + } + set(uint64(kedge), getDummyC(888)) + + // if splitX was wrong kedge:888 would land into wrong place + v, ok = tr.Get(kedge) + if !(v == getDummyC(888) && ok) { + t.Fatalf("after splitX: Get(%v) -> %v, %v ; expected 888, true", kedge, v, ok) + } +} + +func BenchmarkBtreeSetSeq1e3(b *testing.B) { + benchmarkSetSeq(b, 1e3) +} + +func BenchmarkBtreeSetSeq1e4(b *testing.B) { + benchmarkSetSeq(b, 1e4) +} + +func BenchmarkBtreeSetSeq1e5(b *testing.B) { + benchmarkSetSeq(b, 1e5) +} + +func BenchmarkBtreeSetSeq1e6(b *testing.B) { + benchmarkSetSeq(b, 1e6) +} + +func benchmarkSetSeq(b *testing.B, n int) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + r := treeNew() + debug.FreeOSMemory() + b.StartTimer() + for j := 0; j < n; j++ { + r.Set(uint64(j), getDummyC(j)) + } + b.StopTimer() + r.Close() + } + b.StopTimer() +} + +func BenchmarkBtreeGetSeq1e3(b *testing.B) { + benchmarkGetSeq(b, 1e3) +} + +func BenchmarkBtreeGetSeq1e4(b *testing.B) { + benchmarkGetSeq(b, 1e4) +} + +func BenchmarkBtreeGetSeq1e5(b *testing.B) { + benchmarkGetSeq(b, 1e5) +} + +func BenchmarkBtreeGetSeq1e6(b *testing.B) { + benchmarkGetSeq(b, 1e6) +} + +func benchmarkGetSeq(b *testing.B, n int) { + r := treeNew() + for i := 0; i < n; i++ { + r.Set(uint64(i), getDummyC(i)) + } + debug.FreeOSMemory() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < n; j++ { + r.Get(uint64(j)) + } + } + b.StopTimer() + r.Close() +} + +func BenchmarkBtreeSetRnd1e3(b *testing.B) { + benchmarkSetRnd(b, 1e3) +} + +func BenchmarkBtreeSetRnd1e4(b *testing.B) { + benchmarkSetRnd(b, 1e4) +} + +func BenchmarkBtreeSetRnd1e5(b *testing.B) { + benchmarkSetRnd(b, 1e5) +} + +func BenchmarkBtreeSetRnd1e6(b *testing.B) { + benchmarkSetRnd(b, 1e6) +} + +func benchmarkSetRnd(b *testing.B, n int) { + rng := rng() + a := make([]int, n) + for i := range a { + a[i] = rng.Next() + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + r := treeNew() + debug.FreeOSMemory() + b.StartTimer() + for _, v := range a { + r.Set(uint64(v), nil) + } + b.StopTimer() + r.Close() + } + b.StopTimer() +} + +func BenchmarkBtreeGetRnd1e3(b *testing.B) { + benchmarkGetRnd(b, 1e3) +} + +func BenchmarkBtreeGetRnd1e4(b *testing.B) { + benchmarkGetRnd(b, 1e4) +} + +func BenchmarkBtreeGetRnd1e5(b *testing.B) { + benchmarkGetRnd(b, 1e5) +} + +func BenchmarkBtreeGetRnd1e6(b *testing.B) { + benchmarkGetRnd(b, 1e6) +} + +func benchmarkGetRnd(b *testing.B, n int) { + r := treeNew() + rng := rng() + a := make([]int, n) + for i := range a { + a[i] = rng.Next() + } + for _, v := range a { + r.Set(uint64(v), nil) + } + debug.FreeOSMemory() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, v := range a { + r.Get(uint64(v)) + } + } + b.StopTimer() + r.Close() +} + +func TestBtreeSetGet2(t *testing.T) { + const N = 40000 + for _, x := range []int{0, -1, 0x555555, 0xaaaaaa, 0x333333, 0xcccccc, 0x314159} { + rng := rng() + r := treeNew() + set := r.Set + a := make([]int, N) + for i := range a { + a[i] = (rng.Next() ^ x) << 1 + } + for i, k := range a { + set(uint64(k), getDummyC(k^x)) + if g, e := r.Len(), i+1; g != e { + t.Fatal(i, x, g, e) + } + } + + for i, k := range a { + v, ok := r.Get(uint64(k)) + if !ok { + t.Fatal(i, k, v, ok) + } + + if g, e := v, getDummyC(k^x); g != e { + t.Fatal(i, g, e) + } + + k |= 1 + _, ok = r.Get(uint64(k)) + if ok { + t.Fatal(i, k) + } + } + + for _, k := range a { + r.Set(uint64(k), getDummyC((k^x)+42)) + } + + for i, k := range a { + v, ok := r.Get(uint64(k)) + if !ok { + t.Fatal(i, k, v, ok) + } + + if g, e := v, getDummyC(k^x+42); g != e { + t.Fatal(i, g, e) + } + + k |= 1 + _, ok = r.Get(uint64(k)) + if ok { + t.Fatal(i, k) + } + } + } +} + +func TestBtreeSetGet3(t *testing.T) { + r := treeNew() + set := r.Set + var i int + for i = 0; ; i++ { + set(uint64(i), getDummyC(-i)) + if _, ok := r.r.(*x); ok { + break + } + } + for j := 0; j <= i; j++ { + set(uint64(j), getDummyC(j)) + } + + for j := 0; j <= i; j++ { + v, ok := r.Get(uint64(j)) + if !ok { + t.Fatal(j) + } + + if g, e := v, getDummyC(j); g != e { + t.Fatal(g, e) + } + } +} + +func TestBtreeDelete0(t *testing.T) { + r := treeNew() + if ok := r.Delete(0); ok { + t.Fatal(ok) + } + + if g, e := r.Len(), 0; g != e { + t.Fatal(g, e) + } + + r.Set(uint64(0), getDummyC(0)) + if ok := r.Delete(1); ok { + t.Fatal(ok) + } + + if g, e := r.Len(), 1; g != e { + t.Fatal(g, e) + } + + if ok := r.Delete(0); !ok { + t.Fatal(ok) + } + + if g, e := r.Len(), 0; g != e { + t.Fatal(g, e) + } + + if ok := r.Delete(0); ok { + t.Fatal(ok) + } + + r.Set(uint64(0), getDummyC(0)) + r.Set(uint64(1), getDummyC(1)) + if ok := r.Delete(1); !ok { + t.Fatal(ok) + } + + if g, e := r.Len(), 1; g != e { + t.Fatal(g, e) + } + + if ok := r.Delete(1); ok { + t.Fatal(ok) + } + + if ok := r.Delete(0); !ok { + t.Fatal(ok) + } + + if g, e := r.Len(), 0; g != e { + t.Fatal(g, e) + } + + if ok := r.Delete(0); ok { + t.Fatal(ok) + } + + r.Set(uint64(0), getDummyC(0)) + r.Set(uint64(1), getDummyC(1)) + if ok := r.Delete(0); !ok { + t.Fatal(ok) + } + + if g, e := r.Len(), 1; g != e { + t.Fatal(g, e) + } + + if ok := r.Delete(0); ok { + t.Fatal(ok) + } + + if ok := r.Delete(1); !ok { + t.Fatal(ok) + } + + if g, e := r.Len(), 0; g != e { + t.Fatal(g, e) + } + + if ok := r.Delete(1); ok { + t.Fatal(ok) + } +} + +func TestBtreeDelete1(t *testing.T) { + const N = 130000 + for _, x := range []int{0, -1, 0x555555, 0xaaaaaa, 0x333333, 0xcccccc, 0x314159} { + r := treeNew() + set := r.Set + a := make([]int, N) + for i := range a { + a[i] = (i ^ x) << 1 + } + for _, k := range a { + set(uint64(k), getDummyC(0)) + } + + for i, k := range a { + ok := r.Delete(uint64(k)) + if !ok { + t.Fatal(i, x, k) + } + + if g, e := r.Len(), N-i-1; g != e { + t.Fatal(i, g, e) + } + } + } +} + +func BenchmarkBtreeDelSeq1e3(b *testing.B) { + benchmarkDelSeq(b, 1e3) +} + +func BenchmarkBtreeDelSeq1e4(b *testing.B) { + benchmarkDelSeq(b, 1e4) +} + +func BenchmarkBtreeDelSeq1e5(b *testing.B) { + benchmarkDelSeq(b, 1e5) +} + +func BenchmarkBtreeDelSeq1e6(b *testing.B) { + benchmarkDelSeq(b, 1e6) +} + +func benchmarkDelSeq(b *testing.B, n int) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + r := treeNew() + for i := 0; i < n; i++ { + r.Set(uint64(i), getDummyC(i)) + } + debug.FreeOSMemory() + b.StartTimer() + for j := 0; j < n; j++ { + r.Delete(uint64(j)) + } + } + b.StopTimer() +} + +func BenchmarkBtreeDelRnd1e3(b *testing.B) { + benchmarkDelRnd(b, 1e3) +} + +func BenchmarkBtreeDelRnd1e4(b *testing.B) { + benchmarkDelRnd(b, 1e4) +} + +func BenchmarkBtreeDelRnd1e5(b *testing.B) { + benchmarkDelRnd(b, 1e5) +} + +func BenchmarkBtreeDelRnd1e6(b *testing.B) { + benchmarkDelRnd(b, 1e6) +} + +func benchmarkDelRnd(b *testing.B, n int) { + rng := rng() + a := make([]int, n) + for i := range a { + a[i] = rng.Next() + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + r := treeNew() + for _, v := range a { + r.Set(uint64(v), getDummyC(0)) + } + debug.FreeOSMemory() + b.StartTimer() + for _, v := range a { + r.Delete(uint64(v)) + } + b.StopTimer() + r.Close() + } + b.StopTimer() +} + +func TestBtreeDelete2(t *testing.T) { + const N = 100000 + for _, x := range []int{0, -1, 0x555555, 0xaaaaaa, 0x333333, 0xcccccc, 0x314159} { + r := treeNew() + set := r.Set + a := make([]int, N) + rng := rng() + for i := range a { + a[i] = (rng.Next() ^ x) << 1 + } + for _, k := range a { + set(uint64(k), getDummyC(0)) + } + + for i, k := range a { + ok := r.Delete(uint64(k)) + if !ok { + t.Fatal(i, x, k) + } + + if g, e := r.Len(), N-i-1; g != e { + t.Fatal(i, g, e) + } + } + } +} + +func TestBtreeEnumeratorNext(t *testing.T) { + // seeking within 3 keys: 10, 20, 30 + table := []struct { + k int + hit bool + keys []int + }{ + {5, false, []int{10, 20, 30}}, + {10, true, []int{10, 20, 30}}, + {15, false, []int{20, 30}}, + {20, true, []int{20, 30}}, + {25, false, []int{30}}, + {30, true, []int{30}}, + {35, false, []int{}}, + } + + for i, test := range table { + up := test.keys + r := treeNew() + + r.Set(uint64(10), getDummyC(100)) + r.Set(uint64(20), getDummyC(200)) + r.Set(uint64(30), getDummyC(300)) + + for verChange := 0; verChange < 16; verChange++ { + en, hit := r.Seek(uint64(test.k)) + + if g, e := hit, test.hit; g != e { + t.Fatal(i, g, e) + } + + j := 0 + for { + if verChange&(1<= len(up) { + t.Fatal(i, j, verChange) + } + + if g, e := k, uint64(up[j]); g != e { + t.Fatal(i, j, verChange, g, e) + } + + if g, e := v, getDummyC(10*up[j]); g != e { + t.Fatal(i, g, e) + } + + j++ + + } + + if g, e := j, len(up); g != e { + t.Fatal(i, j, g, e) + } + } + + } +} + +func TestBtreeEnumeratorPrev(t *testing.T) { + // seeking within 3 keys: 10, 20, 30 + table := []struct { + k int + hit bool + keys []int + }{ + {5, false, []int{}}, + {10, true, []int{10}}, + {15, false, []int{10}}, + {20, true, []int{20, 10}}, + {25, false, []int{20, 10}}, + {30, true, []int{30, 20, 10}}, + {35, false, []int{30, 20, 10}}, + } + + for i, test := range table { + dn := test.keys + r := treeNew() + + r.Set(uint64(10), getDummyC(100)) + r.Set(uint64(20), getDummyC(200)) + r.Set(uint64(30), getDummyC(300)) + + for verChange := 0; verChange < 16; verChange++ { + en, hit := r.Seek(uint64(test.k)) + + if g, e := hit, test.hit; g != e { + t.Fatal(i, g, e) + } + + j := 0 + for { + if verChange&(1<= len(dn) { + t.Fatal(i, j, verChange) + } + + if g, e := k, uint64(dn[j]); g != e { + t.Fatal(i, j, verChange, g, e) + } + + if g, e := v, getDummyC(10*dn[j]); g != e { + t.Fatal(i, g, e) + } + + j++ + + } + + if g, e := j, len(dn); g != e { + t.Fatal(i, j, g, e) + } + } + + } +} + +func TestBtreeEnumeratorPrevSanity(t *testing.T) { + // seeking within 3 keys: 10, 20, 30 + table := []struct { + k int + hit bool + keyOut uint64 + valueOut *Container + errorOut error + }{ + {10, true, 10, getDummyC(100), nil}, + {20, true, 20, getDummyC(200), nil}, + {30, true, 30, getDummyC(300), nil}, + {35, false, 30, getDummyC(300), nil}, + {25, false, 20, getDummyC(200), nil}, + {15, false, 10, getDummyC(100), nil}, + {5, false, 0, nil, io.EOF}, + } + + for i, test := range table { + r := treeNew() + + r.Set(uint64(10), getDummyC(100)) + r.Set(uint64(20), getDummyC(200)) + r.Set(uint64(30), getDummyC(300)) + + en, hit := r.Seek(uint64(test.k)) + + if g, e := hit, test.hit; g != e { + t.Fatal(i, g, e) + } + + k, v, err := en.Prev() + + if g, e := err, test.errorOut; g != e { + t.Fatal(i, g, e) + } + if g, e := k, test.keyOut; g != e { + t.Fatal(i, g, e) + } + if g, e := v, test.valueOut; g != e { + t.Fatal(i, g, e) + } + } +} + +func BenchmarkBtreeSeekSeq1e3(b *testing.B) { + benchmarkSeekSeq(b, 1e3) +} + +func BenchmarkBtreeSeekSeq1e4(b *testing.B) { + benchmarkSeekSeq(b, 1e4) +} + +func BenchmarkBtreeSeekSeq1e5(b *testing.B) { + benchmarkSeekSeq(b, 1e5) +} + +func BenchmarkBtreeSeekSeq1e6(b *testing.B) { + benchmarkSeekSeq(b, 1e6) +} + +func benchmarkSeekSeq(b *testing.B, n int) { + for i := 0; i < b.N; i++ { + b.StopTimer() + t := treeNew() + for j := 0; j < n; j++ { + t.Set(uint64(j), getDummyC(0)) + } + debug.FreeOSMemory() + b.StartTimer() + for j := 0; j < n; j++ { + e, _ := t.Seek(uint64(j)) + e.Close() + } + b.StopTimer() + t.Close() + } + b.StopTimer() +} + +func BenchmarkBtreeSeekRnd1e3(b *testing.B) { + benchmarkSeekRnd(b, 1e3) +} + +func BenchmarkBtreeSeekRnd1e4(b *testing.B) { + benchmarkSeekRnd(b, 1e4) +} + +func BenchmarkBtreeSeekRnd1e5(b *testing.B) { + benchmarkSeekRnd(b, 1e5) +} + +func BenchmarkBtreeSeekRnd1e6(b *testing.B) { + benchmarkSeekRnd(b, 1e6) +} + +func benchmarkSeekRnd(b *testing.B, n int) { + r := treeNew() + rng := rng() + a := make([]int, n) + for i := range a { + a[i] = rng.Next() + } + for _, v := range a { + r.Set(uint64(v), getDummyC(0)) + } + debug.FreeOSMemory() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, v := range a { + e, _ := r.Seek(uint64(v)) + e.Close() + } + } + b.StopTimer() + r.Close() +} + +func BenchmarkBtreeNext1e3(b *testing.B) { + benchmarkNext(b, 1e3) +} + +func BenchmarkBtreeNext1e4(b *testing.B) { + benchmarkNext(b, 1e4) +} + +func BenchmarkBtreeNext1e5(b *testing.B) { + benchmarkNext(b, 1e5) +} + +func BenchmarkBtreeNext1e6(b *testing.B) { + benchmarkNext(b, 1e6) +} + +func benchmarkNext(b *testing.B, n int) { + t := treeNew() + for i := 0; i < n; i++ { + t.Set(uint64(i), getDummyC(0)) + } + debug.FreeOSMemory() + b.ResetTimer() + for i := 0; i < b.N; i++ { + en, err := t.SeekFirst() + if err != nil { + b.Fatal(err) + } + + m := 0 + for { + if _, _, err = en.Next(); err != nil { + break + } + m++ + } + if m != n { + b.Fatal(m) + } + } + b.StopTimer() + t.Close() +} + +func BenchmarkBtreePrev1e3(b *testing.B) { + benchmarkPrev(b, 1e3) +} + +func BenchmarkBtreePrev1e4(b *testing.B) { + benchmarkPrev(b, 1e4) +} + +func BenchmarkBtreePrev1e5(b *testing.B) { + benchmarkPrev(b, 1e5) +} + +func BenchmarkBtreePrev1e6(b *testing.B) { + benchmarkPrev(b, 1e6) +} + +func benchmarkPrev(b *testing.B, n int) { + t := treeNew() + for i := 0; i < n; i++ { + t.Set(uint64(i), getDummyC(0)) + } + debug.FreeOSMemory() + b.ResetTimer() + for i := 0; i < b.N; i++ { + en, err := t.SeekLast() + if err != nil { + b.Fatal(err) + } + + m := 0 + for { + if _, _, err = en.Prev(); err != nil { + break + } + m++ + } + if m != n { + b.Fatal(m) + } + } +} + +func TestBtreeSeekFirst0(t *testing.T) { + b := treeNew() + _, err := b.SeekFirst() + if g, e := err, io.EOF; g != e { + t.Fatal(g, e) + } +} + +func TestBtreeSeekFirst1(t *testing.T) { + b := treeNew() + b.Set(uint64(1), getDummyC(10)) + en, err := b.SeekFirst() + if err != nil { + t.Fatal(err) + } + + k, v, err := en.Next() + if k != 1 || v != getDummyC(10) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Next() + if err == nil { + t.Fatal(k, v, err) + } +} + +func TestBtreeSeekFirst2(t *testing.T) { + b := treeNew() + b.Set(uint64(1), getDummyC(10)) + b.Set(uint64(2), getDummyC(20)) + en, err := b.SeekFirst() + if err != nil { + t.Fatal(err) + } + + k, v, err := en.Next() + if k != 1 || v != getDummyC(10) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Next() + if k != 2 || v != getDummyC(20) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Next() + if err == nil { + t.Fatal(k, v, err) + } +} + +func TestBtreeSeekFirst3(t *testing.T) { + b := treeNew() + b.Set(uint64(2), getDummyC(20)) + b.Set(uint64(3), getDummyC(30)) + b.Set(uint64(1), getDummyC(10)) + en, err := b.SeekFirst() + if err != nil { + t.Fatal(err) + } + + k, v, err := en.Next() + if k != 1 || v != getDummyC(10) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Next() + if k != 2 || v != getDummyC(20) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Next() + if k != 3 || v != getDummyC(30) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Next() + if err == nil { + t.Fatal(k, v, err) + } +} + +func TestBtreeSeekLast0(t *testing.T) { + b := treeNew() + _, err := b.SeekLast() + if g, e := err, io.EOF; g != e { + t.Fatal(g, e) + } +} + +func TestBtreeSeekLast1(t *testing.T) { + b := treeNew() + b.Set(uint64(1), getDummyC(10)) + en, err := b.SeekLast() + if err != nil { + t.Fatal(err) + } + + k, v, err := en.Prev() + if k != 1 || v != getDummyC(10) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Prev() + if err == nil { + t.Fatal(k, v, err) + } +} + +func TestBtreeSeekLast2(t *testing.T) { + b := treeNew() + b.Set(uint64(1), getDummyC(10)) + b.Set(uint64(2), getDummyC(20)) + en, err := b.SeekLast() + if err != nil { + t.Fatal(err) + } + + k, v, err := en.Prev() + if k != 2 || v != getDummyC(20) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Prev() + if k != 1 || v != getDummyC(10) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Prev() + if err == nil { + t.Fatal(k, v, err) + } +} + +func TestBtreeSeekLast3(t *testing.T) { + b := treeNew() + b.Set(uint64(2), getDummyC(20)) + b.Set(uint64(3), getDummyC(30)) + b.Set(uint64(1), getDummyC(10)) + en, err := b.SeekLast() + if err != nil { + t.Fatal(err) + } + + k, v, err := en.Prev() + if k != 3 || v != getDummyC(30) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Prev() + if k != 2 || v != getDummyC(20) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Prev() + if k != 1 || v != getDummyC(10) || err != nil { + t.Fatal(k, v, err) + } + + k, v, err = en.Prev() + if err == nil { + t.Fatal(k, v, err) + } +} + +func TestBtreePut(t *testing.T) { + tab := []struct { + pre []int // even index: K, odd index: V + newK int // Put(newK, ... + oldV int // Put()->oldV + exists bool // upd(exists) + write bool // upd()->write + post []int // even index: K, odd index: V + }{ + // 0 + { + []int{}, + 1, 0, false, false, + []int{}, + }, + { + []int{}, + 1, 0, false, true, + []int{1, -1}, + }, + { + []int{1, 10}, + 0, 0, false, false, + []int{1, 10}, + }, + { + []int{1, 10}, + 0, 0, false, true, + []int{0, -1, 1, 10}, + }, + { + []int{1, 10}, + 1, 10, true, false, + []int{1, 10}, + }, + + // 5 + { + []int{1, 10}, + 1, 10, true, true, + []int{1, -1}, + }, + { + []int{1, 10}, + 2, 0, false, false, + []int{1, 10}, + }, + { + []int{1, 10}, + 2, 0, false, true, + []int{1, 10, 2, -1}, + }, + } + + for iTest, test := range tab { + tr := treeNew() + for i := 0; i < len(test.pre); i += 2 { + k, v := test.pre[i], test.pre[i+1] + tr.Set(uint64(k), getDummyC(v)) + } + + oldV, written := tr.Put(uint64(test.newK), func(old *Container, exists bool) (newV *Container, write bool) { + if g, e := exists, test.exists; g != e { + t.Fatal(iTest, g, e) + } + + if exists { + if g, e := old, getDummyC(test.oldV); g != e { + t.Fatal(iTest, g, e) + } + } + return nil, test.write + }) + if test.exists { + if g, e := oldV, getDummyC(test.oldV); g != e { + t.Fatal(iTest, g, e) + } + } + + if g, e := written, test.write; g != e { + t.Fatal(iTest, g, e) + } + + n := len(test.post) + en, err := tr.SeekFirst() + if err != nil { + if n == 0 && err == io.EOF { + continue + } + + t.Fatal(iTest, err) + } + + for i := 0; i < len(test.post); i += 2 { + k, v, err := en.Next() + if err != nil { + t.Fatal(iTest, err) + } + + if g, e := k, uint64(test.post[i]); g != e { + t.Fatal(iTest, g, e) + } + + var e *Container + if test.post[i+1] != -1 { + e = getDummyC(test.post[i+1]) + } + if g := v; g != e { + t.Fatal(iTest, g, e) + } + } + + _, _, err = en.Next() + if g, e := err, io.EOF; g != e { + t.Fatal(iTest, g, e) + } + } +} + +func TestBtreeSeek(t *testing.T) { + const N = 1 << 13 + tr := treeNew() + for i := 0; i < N; i++ { + k := 2*i + 1 + tr.Set(uint64(k), nil) + } + for i := 0; i < N; i++ { + k := 2 * i + e, ok := tr.Seek(uint64(k)) + if ok { + t.Fatal(k) + } + + for j := i; j < N; j++ { + k2, _, err := e.Next() + if err != nil { + t.Fatal(k, err) + } + + if g, e := k2, uint64(2*j+1); g != e { + t.Fatal(j, g, e) + } + } + + _, _, err := e.Next() + if err != io.EOF { + t.Fatalf("expected io.EOF, got %v", err) + } + } +} + +func TestBtreePR4(t *testing.T) { + tr := treeNew() + for i := 0; i < 2*kd+1; i++ { + k := 1000 * i + tr.Set(uint64(k), nil) + } + tr.Delete(1000 * kd) + for i := 0; i < kd; i++ { + tr.Set(uint64(1000*(kd+1)-1-i), nil) + } + k := 1000*(kd+1) - 1 - kd + tr.Set(uint64(k), nil) + if _, ok := tr.Get(uint64(k)); !ok { + t.Fatalf("key lost: %v", k) + } +} diff --git a/roaring/containers_btree.go b/roaring/containers_btree.go index 04fc6befa..fb29a6d08 100644 --- a/roaring/containers_btree.go +++ b/roaring/containers_btree.go @@ -18,10 +18,6 @@ import ( "io" ) -func cmp(a, b uint64) int64 { - return int64(a - b) -} - type bTreeContainers struct { tree *tree @@ -31,7 +27,7 @@ type bTreeContainers struct { func newBTreeContainers() *bTreeContainers { return &bTreeContainers{ - tree: treeNew(cmp), + tree: treeNew(), } } @@ -158,7 +154,7 @@ func (btc *bTreeContainers) Size() int { } func (btc *bTreeContainers) Reset() { - btc.tree = treeNew(cmp) + btc.tree = treeNew() btc.lastKey = 0 btc.lastContainer = nil } diff --git a/roaring/inst.go b/roaring/inst.go new file mode 100644 index 000000000..c0a5771d9 --- /dev/null +++ b/roaring/inst.go @@ -0,0 +1,27 @@ +// +build btreeInstrumentation + +package roaring + +type dTree struct { + t *tree +} + +type treeInst struct { + deCopied int64 +} + +func (t *tree) didCopy(n int) { + t.deCopied += int64(n) +} + +func (d *d) didCopy(n int) { + d.t.deCopied += int64(n) +} + +func (t *tree) countCopies() int64 { + return t.deCopied +} + +func (d *d) setTree(t *tree) { + d.t = t +} diff --git a/roaring/nop_inst.go b/roaring/nop_inst.go new file mode 100644 index 000000000..7a945cb6d --- /dev/null +++ b/roaring/nop_inst.go @@ -0,0 +1,24 @@ +// +build !btreeInstrumentation + +package roaring + +//lint:ignore U1000 this is conditional on a build flag +type dTree struct { +} + +//lint:ignore U1000 this is conditional on a build flag +type treeInst struct { +} + +func (t *tree) didCopy(n int) { +} + +func (d *d) didCopy(n int) { +} + +func (t *tree) countCopies() int64 { + return 0 +} + +func (d *d) setTree(t *tree) { +}