From cfb2a80866ca1c4f1432e66f8059c96b12bc49be Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Tue, 5 Mar 2019 08:48:33 -0600 Subject: [PATCH] write large fragment import benchmark needed to pull in btree containers to get acceptable perf building the initial data. Still quite slow though. --- fragment_internal_test.go | 108 +++- roaring/btree.go | 951 ++++++++++++++++++++++++++++++++++++ roaring/containers_btree.go | 209 ++++++++ roaring/roaring.go | 2 +- 4 files changed, 1259 insertions(+), 11 deletions(-) create mode 100644 roaring/btree.go create mode 100644 roaring/containers_btree.go diff --git a/fragment_internal_test.go b/fragment_internal_test.go index 6d1aad8c6..3a528c840 100644 --- a/fragment_internal_test.go +++ b/fragment_internal_test.go @@ -18,6 +18,7 @@ import ( "bytes" "flag" "fmt" + "io" "io/ioutil" "math" "math/rand" @@ -2101,6 +2102,66 @@ func BenchmarkImportRoaringUpdate(b *testing.B) { } } +var bigFrag string + +func initBigFrag() { + if bigFrag == "" { + f := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) + for i := int64(0); i < 10; i++ { + // 10 million rows, 1 bit per column, random seeded by i + data := getZipfRowsSliceRoaring(10000000, i) + err := f.importRoaring(data, false) + if err != nil { + panic(fmt.Sprintf("setting up fragment data: %v", err)) + } + } + err := f.Close() + if err != nil { + panic(fmt.Sprintf("closing fragment: %v", err)) + } + bigFrag = f.path + } +} + +func BenchmarkImportIntoLargeFragment(b *testing.B) { + b.StopTimer() + initBigFrag() + rowsOrig, colsOrig := getUpdataSlices(10000000, 11000, 0) + rows, cols := make([]uint64, len(rowsOrig)), make([]uint64, len(colsOrig)) + opts := &ImportOptions{} + for i := 0; i < b.N; i++ { + origF, err := os.Open(bigFrag) + if err != nil { + b.Fatalf("opening frag file: %v", err) + } + fi, err := ioutil.TempFile(*TempDir, "") + if err != nil { + b.Fatalf("getting temp file: %v", err) + } + _, err = io.Copy(fi, origF) + if err != nil { + b.Fatalf("copying fragment file: %v", err) + } + origF.Close() + fi.Close() + nf := newFragment(fi.Name(), "i", "f", viewStandard, 0) + err = nf.Open() + if err != nil { + b.Fatalf("opening fragment: %v", err) + } + copy(rows, rowsOrig) + copy(cols, colsOrig) + b.StartTimer() + err = nf.bulkImport(rows, cols, opts) + b.StopTimer() + if err != nil { + b.Fatalf("bulkImport: %v", err) + } + + nf.Clean(b) + } +} + func TestGetZipfRowsSliceRoaring(t *testing.T) { f := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) data := getZipfRowsSliceRoaring(10, 1) @@ -2119,16 +2180,23 @@ func TestGetZipfRowsSliceRoaring(t *testing.T) { // rows, and 1 bit set in each column. The row each bit is set in is chosen via // the Zipf generator, and so will be skewed toward lower row numbers. If this // is edited to change the data distribution, getZipfRowsSliceStandard should be -// edited as well. +// edited as well. TODO switch to generating row-major for perf boost func getZipfRowsSliceRoaring(numRows uint64, seed int64) []byte { - b := roaring.NewBitmap() + b := roaring.NewBTreeBitmap() s := rand.NewSource(seed) r := rand.New(s) z := rand.NewZipf(r, 1.6, 50, numRows-1) + bufSize := 1 << 14 + posBuf := make([]uint64, 0, bufSize) for i := uint64(0); i < ShardWidth; i++ { row := z.Uint64() - b.DirectAdd(row*ShardWidth + i) + posBuf = append(posBuf, row*ShardWidth+i) + if len(posBuf) == bufSize { + sort.Slice(posBuf, func(i int, j int) bool { return posBuf[i] < posBuf[j] }) + b.DirectAddN(posBuf...) + } } + b.DirectAddN(posBuf...) buf := bytes.NewBuffer(make([]byte, 0, 100000)) _, err := b.WriteTo(buf) if err != nil { @@ -2137,11 +2205,34 @@ func getZipfRowsSliceRoaring(numRows uint64, seed int64) []byte { return buf.Bytes() } +func getUpdataSlices(numRows, numCols uint64, seed int64) (rows, cols []uint64) { + getUpdataInto(func(row, col uint64) bool { + rows = append(rows, row) + cols = append(cols, col) + return true + }, numRows, numCols, seed) + return rows, cols +} + // getUpdataRoaring gets a byte slice containing a roaring bitmap which // represents numCols set bits distributed randomly throughout a shard's column // space and zipfianly throughout numRows rows. func getUpdataRoaring(numRows, numCols uint64, seed int64) []byte { b := roaring.NewBitmap() + + getUpdataInto(func(row, col uint64) bool { + return b.DirectAdd(row*ShardWidth + col) + }, numRows, numCols, seed) + + buf := bytes.NewBuffer(make([]byte, 0, 100000)) + _, err := b.WriteTo(buf) + if err != nil { + panic(err) + } + return buf.Bytes() +} + +func getUpdataInto(f func(row, col uint64) bool, numRows, numCols uint64, seed int64) (changed int) { s := rand.NewSource(seed) r := rand.New(s) z := rand.NewZipf(r, 1.6, 50, numRows-1) @@ -2149,14 +2240,11 @@ func getUpdataRoaring(numRows, numCols uint64, seed int64) []byte { for i := uint64(0); i < numCols; i++ { col := uint64(r.Int63n(ShardWidth)) // assuming the number of repeats will be negligible row := z.Uint64() - b.DirectAdd(row*ShardWidth + col) + if f(row, col) { + changed++ + } } - buf := bytes.NewBuffer(make([]byte, 0, 100000)) - _, err := b.WriteTo(buf) - if err != nil { - panic(err) - } - return buf.Bytes() + return changed } // getZipfRowsSliceStandard is the same as getZipfRowsSliceRoaring, but returns diff --git a/roaring/btree.go b/roaring/btree.go new file mode 100644 index 000000000..caa0bb46e --- /dev/null +++ b/roaring/btree.go @@ -0,0 +1,951 @@ +// This file is a modified redistribution of b (https://github.com/cznic/b), +// which is governed by the following license notice: +// +// Copyright (c) 2014 The b Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the names of the authors nor the names of the +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package roaring + +import ( + "io" + "sync" +) + +const ( + // kx must be >= 2 + kx = 128 //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). +) + +var ( + btDPool = sync.Pool{New: func() interface{} { return &d{} }} + btEPool = btEpool{sync.Pool{New: func() interface{} { return &enumerator{} }}} + btTPool = btTpool{sync.Pool{New: func() interface{} { return &tree{} }}} + btXPool = sync.Pool{New: func() interface{} { return &x{} }} +) + +type btTpool struct{ sync.Pool } + +func (p *btTpool) get(cmp Cmp) *tree { + x := p.Get().(*tree) + x.cmp = cmp + return x +} + +type btEpool struct{ sync.Pool } + +func (p *btEpool) get(err error, hit bool, i int, k uint64, q *d, t *tree, ver int64) *enumerator { + x := p.Get().(*enumerator) + x.err, x.hit, x.i, x.k, x.q, x.t, x.ver = err, hit, i, k, q, t, ver + return x +} + +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 + } + + de struct { // d element + k uint64 + v *Container + } + + // enumerator captures the state of enumerating a tree. It is returned + // from the Seek* methods. The enumerator is aware of any mutations + // made to the tree in the process of enumerating it and automatically + // resumes the enumeration at the proper key, if possible. + // + // However, once an enumerator returns io.EOF to signal "no more + // items", it does no more attempt to "resync" on tree mutation(s). In + // other words, io.EOF from an enumerator is "sticky" (idempotent). + enumerator struct { + err error + hit bool + i int + k uint64 + q *d + t *tree + ver int64 + } + + // tree is a B+tree. + tree struct { + c int + cmp Cmp + first *d + last *d + r interface{} + ver int64 + } + + xe struct { // x element + ch interface{} + k uint64 + } + + x struct { // index page + c int + x [2*kx + 2]xe + } +) + +var ( // R/O zero values + zd d + zde de + ze enumerator + zk uint64 + zt tree + zx x + zxe xe +) + +func clr(q interface{}) { + switch x := q.(type) { + case *x: + for i := 0; i <= x.c; i++ { // Ch0 Sep0 ... Chn-1 Sepn-1 Chn + clr(x.x[i].ch) + } + *x = zx + btXPool.Put(x) + case *d: + *x = zd + btDPool.Put(x) + } +} + +// -------------------------------------------------------------------------- x + +func newX(ch0 interface{}) *x { + r := btXPool.Get().(*x) + r.x[0].ch = ch0 + return r +} + +func (q *x) extract(i int) { + q.c-- + if i < q.c { + copy(q.x[i:], q.x[i+1:q.c+1]) + q.x[q.c].ch = q.x[q.c+1].ch + q.x[q.c].k = zk // GC + q.x[q.c+1] = zxe // GC + } +} + +func (q *x) insert(i int, k uint64, ch interface{}) *x { + c := q.c + if i < c { + q.x[c+1].ch = q.x[c].ch + copy(q.x[i+2:], q.x[i+1:c]) + q.x[i+1].k = q.x[i].k + } + c++ + q.c = c + q.x[i].k = k + q.x[i+1].ch = ch + return q +} + +func (q *x) siblings(i int) (l, r *d) { + if i >= 0 { + if i > 0 { + l = q.x[i-1].ch.(*d) + } + if i < q.c { + r = q.x[i+1].ch.(*d) + } + } + return l, r +} + +// -------------------------------------------------------------------------- d + +func (l *d) mvL(r *d, c int) { + 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 + // and to avoid creating non-collectible (GC) references. + for i := 1; i < c; i++ { + r.d[r.c-i] = zde + } + l.c += c + r.c -= c +} + +func (l *d) mvR(r *d, c int) { + 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 + // and to avoid creating non-collectible (GC) references. + for i := 1; i < c; i++ { + l.d[l.c-c+i] = zde + } + r.c += c + l.c -= c +} + +// ----------------------------------------------------------------------- Tree + +// treeNew returns a newly created, empty Tree. The compare function is used +// for key collation. +func treeNew(cmp Cmp) *tree { + return btTPool.get(cmp) +} + +// Clear removes all K/V pairs from the tree. +func (t *tree) Clear() { + if t.r == nil { + return + } + + clr(t.r) + t.c, t.first, t.last, t.r = 0, nil, nil, nil + t.ver++ +} + +// Close performs Clear and recycles t to a pool for possible later reuse. No +// references to t should exist or such references must not be used afterwards. +func (t *tree) Close() { + t.Clear() + *t = zt + btTPool.Put(t) +} + +func (t *tree) cat(p *x, q, r *d, pi int) { + t.ver++ + q.mvL(r, r.c) + if r.n != nil { + r.n.p = q + } else { + t.last = q + } + q.n = r.n + *r = zd + btDPool.Put(r) + if p.c > 1 { + p.extract(pi) + p.x[pi].ch = q + return + } + + switch x := t.r.(type) { + case *x: + *x = zx + btXPool.Put(x) + case *d: + *x = zd + btDPool.Put(x) + } + t.r = q +} + +func (t *tree) catX(p, q, r *x, pi int) { + t.ver++ + q.x[q.c].k = p.x[pi].k + copy(q.x[q.c+1:], r.x[:r.c]) + q.c += r.c + 1 + q.x[q.c].ch = r.x[r.c].ch + *r = zx + btXPool.Put(r) + if p.c > 1 { + p.c-- + pc := p.c + if pi < pc { + p.x[pi].k = p.x[pi+1].k + copy(p.x[pi+1:], p.x[pi+2:pc+1]) + p.x[pc].ch = p.x[pc+1].ch + p.x[pc].k = zk // GC + p.x[pc+1].ch = nil // GC + } + return + } + + switch x := t.r.(type) { + case *x: + *x = zx + btXPool.Put(x) + case *d: + *x = zd + btDPool.Put(x) + } + t.r = q +} + +// Delete removes the k's KV pair, if it exists, in which case Delete returns +// true. +func (t *tree) Delete(k uint64) (ok bool) { + pi := -1 + var p *x + q := t.r + if q == nil { + return false + } + + for { + var i int + i, ok = t.find(q, k) + if ok { + switch x := q.(type) { + case *x: + if x.c < kx && q != t.r { + x, i = t.underflowX(p, x, pi, i) + } + pi = i + 1 + p = x + q = x.x[pi].ch + continue + case *d: + t.extract(x, i) + if x.c >= kd { + return true + } + + if q != t.r { + t.underflow(p, x, pi) + } else if t.c == 0 { + t.Clear() + } + return true + } + } + + switch x := q.(type) { + case *x: + if x.c < kx && q != t.r { + x, i = t.underflowX(p, x, pi, i) + } + pi = i + p = x + q = x.x[i].ch + case *d: + return false + } + } +} + +func (t *tree) extract(q *d, i int) { // (r *container) { + t.ver++ + //r = q.d[i].v // prepared for Extract + q.c-- + if i < q.c { + copy(q.d[i:], q.d[i+1:q.c+1]) + } + q.d[q.c] = zde // GC + t.c-- +} + +func (t *tree) find(q interface{}, k uint64) (i int, ok bool) { + var mk uint64 + l := 0 + switch x := q.(type) { + case *x: + h := x.c - 1 + for l <= h { + m := (l + h) >> 1 + mk = x.x[m].k + switch cmp := t.cmp(k, mk); { + case cmp > 0: + l = m + 1 + case cmp == 0: + return m, true + default: + h = m - 1 + } + } + case *d: + h := x.c - 1 + for l <= h { + m := (l + h) >> 1 + mk = x.d[m].k + switch cmp := t.cmp(k, mk); { + case cmp > 0: + l = m + 1 + case cmp == 0: + return m, true + default: + h = m - 1 + } + } + } + return l, false +} + +// First returns the first item of the tree in the key collating order, or +// (zero-value, zero-value) if the tree is empty. +func (t *tree) First() (k uint64, v *Container) { + if q := t.first; q != nil { + q := &q.d[0] + k, v = q.k, q.v + } + return k, v +} + +// Get returns the value associated with k and true if it exists. Otherwise Get +// returns (zero-value, false). +func (t *tree) Get(k uint64) (v *Container, ok bool) { + q := t.r + if q == nil { + return + } + + for { + var i int + if i, ok = t.find(q, k); ok { + switch x := q.(type) { + case *x: + q = x.x[i+1].ch + continue + case *d: + return x.d[i].v, true + } + } + switch x := q.(type) { + case *x: + q = x.x[i].ch + default: + return + } + } +} + +func (t *tree) insert(q *d, i int, k uint64, v *Container) *d { + t.ver++ + c := q.c + if i < c { + copy(q.d[i+1:], q.d[i:c]) + } + c++ + q.c = c + q.d[i].k, q.d[i].v = k, v + t.c++ + return q +} + +// Last returns the last item of the tree in the key collating order, or +// (zero-value, zero-value) if the tree is empty. +func (t *tree) Last() (k uint64, v *Container) { + if q := t.last; q != nil { + q := &q.d[q.c-1] + k, v = q.k, q.v + } + return k, v +} + +// Len returns the number of items in the tree. +func (t *tree) Len() int { + return t.c +} + +func (t *tree) overflow(p *x, q *d, pi, i int, k uint64, v *Container) { + t.ver++ + l, r := p.siblings(pi) + + // s is the number of items to shift out of the full data container to + // allow for the new data item. This logic shifts by half the available + // space plus one. In the case where the new item is to be inserted within + // the calculated shift space, then s is reduced to include only the + // data items up to the index of the new data item. + if l != nil && l.c < 2*kd && i != 0 { + s := (2*kd-l.c)/2 + 1 // half plus one + //s := 2*kd - l.c // all available + if i < s { + s = i + } + l.mvL(q, s) + t.insert(q, i-s, k, v) + p.x[pi-1].k = q.d[0].k + return + } + + if r != nil && r.c < 2*kd { + if i < 2*kd { + s := (2*kd-r.c)/2 + 1 // half plus one + //s := 2*kd - r.c // all available + if 2*kd-i < s { + s = 2*kd - i + } + q.mvR(r, s) + t.insert(q, i, k, v) + p.x[pi].k = r.d[0].k + return + } + + t.insert(r, 0, k, v) + p.x[pi].k = k + return + } + + t.split(p, q, pi, i, k, v) +} + +// Seek returns an Enumerator positioned on an item such that k >= item's key. +// ok reports if k == item.key The Enumerator's position is possibly after the +// last item in the tree. +func (t *tree) Seek(k uint64) (e *enumerator, ok bool) { + q := t.r + if q == nil { + e = btEPool.get(nil, false, 0, k, nil, t, t.ver) + return + } + + for { + var i int + if i, ok = t.find(q, k); ok { + switch x := q.(type) { + case *x: + q = x.x[i+1].ch + continue + case *d: + return btEPool.get(nil, ok, i, k, x, t, t.ver), true + } + } + + switch x := q.(type) { + case *x: + q = x.x[i].ch + case *d: + return btEPool.get(nil, ok, i, k, x, t, t.ver), false + } + } +} + +// SeekFirst returns an enumerator positioned on the first KV pair in the tree, +// if any. For an empty tree, err == io.EOF is returned and e will be nil. +func (t *tree) SeekFirst() (e *enumerator, err error) { + q := t.first + if q == nil { + return nil, io.EOF + } + + return btEPool.get(nil, true, 0, q.d[0].k, q, t, t.ver), nil +} + +// SeekLast returns an enumerator positioned on the last KV pair in the tree, +// if any. For an empty tree, err == io.EOF is returned and e will be nil. +func (t *tree) SeekLast() (e *enumerator, err error) { + q := t.last + if q == nil { + return nil, io.EOF + } + + return btEPool.get(nil, true, q.c-1, q.d[q.c-1].k, q, t, t.ver), nil +} + +// Set sets the value associated with k. +func (t *tree) Set(k uint64, v *Container) { + //dbg("--- PRE Set(%v, %v)\n%s", k, v, t.dump()) + //defer func() { + // dbg("--- POST\n%s\n====\n", t.dump()) + //}() + + pi := -1 + var p *x + q := t.r + if q == nil { + z := t.insert(btDPool.Get().(*d), 0, k, v) + t.r, t.first, t.last = z, z, z + return + } + + for { + i, ok := t.find(q, k) + if ok { + switch x := q.(type) { + case *x: + i++ + if x.c > 2*kx { + x, i = t.splitX(p, x, pi, i) + } + pi = i + p = x + q = x.x[i].ch + continue + case *d: + x.d[i].v = v + } + return + } + + switch x := q.(type) { + case *x: + if x.c > 2*kx { + x, i = t.splitX(p, x, pi, i) + } + pi = i + p = x + q = x.x[i].ch + case *d: + switch { + case x.c < 2*kd: + t.insert(x, i, k, v) + default: + t.overflow(p, x, pi, i, k, v) + } + return + } + } +} + +// Put combines Get and Set in a more efficient way where the tree is walked +// only once. The upd(ater) receives (old-value, true) if a KV pair for k +// exists or (zero-value, false) otherwise. It can then return a (new-value, +// true) to create or overwrite the existing value in the KV pair, or +// (whatever, false) if it decides not to create or not to update the value of +// the KV pair. +// +// tree.Set(k, v) call conceptually equals calling +// +// tree.Put(k, func(uint64, bool){ return v, true }) +// +// modulo the differing return values. +func (t *tree) Put(k uint64, upd func(oldV *Container, exists bool) (newV *Container, write bool)) (oldV *Container, written bool) { + pi := -1 + var p *x + q := t.r + var newV *Container + if q == nil { + // new KV pair in empty tree + newV, written = upd(newV, false) + if !written { + return + } + + z := t.insert(btDPool.Get().(*d), 0, k, newV) + t.r, t.first, t.last = z, z, z + return + } + + for { + i, ok := t.find(q, k) + if ok { + switch x := q.(type) { + case *x: + i++ + if x.c > 2*kx { + x, i = t.splitX(p, x, pi, i) + } + pi = i + p = x + q = x.x[i].ch + continue + case *d: + oldV = x.d[i].v + newV, written = upd(oldV, true) + if !written { + return + } + + x.d[i].v = newV + } + return + } + + switch x := q.(type) { + case *x: + if x.c > 2*kx { + x, i = t.splitX(p, x, pi, i) + } + pi = i + p = x + q = x.x[i].ch + case *d: // new KV pair + newV, written = upd(newV, false) + if !written { + return + } + + switch { + case x.c < 2*kd: + t.insert(x, i, k, newV) + default: + t.overflow(p, x, pi, i, k, newV) + } + return + } + } +} + +func (t *tree) split(p *x, q *d, pi, i int, k uint64, v *Container) { + t.ver++ + r := btDPool.Get().(*d) + if q.n != nil { + r.n = q.n + r.n.p = r + } else { + t.last = r + } + q.n = r + r.p = q + + copy(r.d[:], q.d[kd:2*kd]) + for i := range q.d[kd:] { + q.d[kd+i] = zde + } + q.c = kd + r.c = kd + var done bool + if i > kd { + done = true + t.insert(r, i-kd, k, v) + } + if pi >= 0 { + p.insert(pi, r.d[0].k, r) + } else { + t.r = newX(q).insert(0, r.d[0].k, r) + } + if done { + return + } + + t.insert(q, i, k, v) +} + +func (t *tree) splitX(p *x, q *x, pi int, i int) (*x, int) { + t.ver++ + r := btXPool.Get().(*x) + copy(r.x[:], q.x[kx+1:]) + q.c = kx + r.c = kx + if pi >= 0 { + p.insert(pi, q.x[kx].k, r) + } else { + t.r = newX(q).insert(0, q.x[kx].k, r) + } + + q.x[kx].k = zk + for i := range q.x[kx+1:] { + q.x[kx+i+1] = zxe + } + if i > kx { + q = r + i -= kx + 1 + } + + return q, i +} + +func (t *tree) underflow(p *x, q *d, pi int) { + t.ver++ + l, r := p.siblings(pi) + + if l != nil && l.c+q.c >= 2*kd { + l.mvR(q, 1) + p.x[pi-1].k = q.d[0].k + return + } + + if r != nil && q.c+r.c >= 2*kd { + q.mvL(r, 1) + p.x[pi].k = r.d[0].k + r.d[r.c] = zde // GC + return + } + + if l != nil { + t.cat(p, l, q, pi-1) + return + } + + t.cat(p, q, r, pi) +} + +func (t *tree) underflowX(p *x, q *x, pi int, i int) (*x, int) { + t.ver++ + var l, r *x + + if pi >= 0 { + if pi > 0 { + l = p.x[pi-1].ch.(*x) + } + if pi < p.c { + r = p.x[pi+1].ch.(*x) + } + } + + if l != nil && l.c > kx { + q.x[q.c+1].ch = q.x[q.c].ch + copy(q.x[1:], q.x[:q.c]) + q.x[0].ch = l.x[l.c].ch + q.x[0].k = p.x[pi-1].k + q.c++ + i++ + l.c-- + p.x[pi-1].k = l.x[l.c].k + return q, i + } + + if r != nil && r.c > kx { + q.x[q.c].k = p.x[pi].k + q.c++ + q.x[q.c].ch = r.x[0].ch + p.x[pi].k = r.x[0].k + copy(r.x[:], r.x[1:r.c]) + r.c-- + rc := r.c + r.x[rc].ch = r.x[rc+1].ch + r.x[rc].k = zk + r.x[rc+1].ch = nil + return q, i + } + + if l != nil { + i += l.c + 1 + t.catX(p, l, q, pi-1) + q = l + return q, i + } + + t.catX(p, q, r, pi) + return q, i +} + +// ----------------------------------------------------------------- Enumerator + +// Close recycles e to a pool for possible later reuse. No references to e +// should exist or such references must not be used afterwards. +func (e *enumerator) Close() { + *e = ze + btEPool.Put(e) +} + +// Next returns the currently enumerated item, if it exists and moves to the +// next item in the key collation order. If there is no item to return, err == +// io.EOF is returned. +func (e *enumerator) Next() (k uint64, v *Container, err error) { + if err = e.err; err != nil { + return 0, nil, err + } + + if e.ver != e.t.ver { + f, _ := e.t.Seek(e.k) + *e = *f + f.Close() + } + if e.q == nil { + e.err, err = io.EOF, io.EOF + return 0, nil, err + } + + if e.i >= e.q.c { + if err = e.next(); err != nil { + return 0, nil, err + } + } + + i := e.q.d[e.i] + k, v = i.k, i.v + e.k, e.hit = k, true + e.next() + return k, v, nil +} + +func (e *enumerator) next() error { + if e.q == nil { + e.err = io.EOF + return io.EOF + } + + switch { + case e.i < e.q.c-1: + e.i++ + default: + if e.q, e.i = e.q.n, 0; e.q == nil { + e.err = io.EOF + } + } + return e.err +} + +// Prev returns the currently enumerated item, if it exists and moves to the +// previous item in the key collation order. If there is no item to return, err +// == io.EOF is returned. +func (e *enumerator) Prev() (k uint64, v *Container, err error) { + if err = e.err; err != nil { + return 0, nil, err + } + + if e.ver != e.t.ver { + f, _ := e.t.Seek(e.k) + *e = *f + f.Close() + } + if e.q == nil { + e.err, err = io.EOF, io.EOF + return 0, nil, err + } + + if !e.hit { + // move to previous because Seek overshoots if there's no hit + if err = e.prev(); err != nil { + return 0, nil, err + } + } + + if e.i >= e.q.c { + if err = e.prev(); err != nil { + return 0, nil, err + } + } + + i := e.q.d[e.i] + k, v = i.k, i.v + e.k, e.hit = k, true + e.prev() + return k, v, err +} + +func (e *enumerator) prev() error { + if e.q == nil { + e.err = io.EOF + return io.EOF + } + + switch { + case e.i > 0: + e.i-- + default: + if e.q = e.q.p; e.q == nil { + e.err = io.EOF + break + } + + e.i = e.q.c - 1 + } + return e.err +} diff --git a/roaring/containers_btree.go b/roaring/containers_btree.go new file mode 100644 index 000000000..887f5b9a9 --- /dev/null +++ b/roaring/containers_btree.go @@ -0,0 +1,209 @@ +// Copyright (c) 2018 Pilosa Corp. All rights reserved. +// +// This file is part of Pilosa Enterprise Edition. +// +// Pilosa Enterprise Edition is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Pilosa Enterprise Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Pilosa Enterprise Edition. If not, see . + +package roaring + +import ( + "io" +) + +func cmp(a, b uint64) int64 { + return int64(a - b) +} + +type bTreeContainers struct { + tree *tree + + lastKey uint64 + lastContainer *Container +} + +func newBTreeContainers() *bTreeContainers { + return &bTreeContainers{ + tree: treeNew(cmp), + } +} + +func NewBTreeBitmap(a ...uint64) *Bitmap { + b := &Bitmap{ + Containers: newBTreeContainers(), + } + b.Add(a...) + return b +} + +func (btc *bTreeContainers) Get(key uint64) *Container { + // Check the last* cache for same container. + if key == btc.lastKey && btc.lastContainer != nil { + return btc.lastContainer + } + + var c *Container + el, ok := btc.tree.Get(key) + if ok { + c = el + btc.lastKey = key + btc.lastContainer = c + } + return c +} + +func (btc *bTreeContainers) Put(key uint64, c *Container) { + // If a mapped container is added to the tree, reset the + // lastContainer cache so that the cache is not pointing + // at a read-only mmap. + if c.Mapped() { + btc.lastContainer = nil + } + btc.tree.Set(key, c) +} + +func (u updater) update(oldV *Container, exists bool) (*Container, bool) { + // update the existing container + if exists { + oldV.Update(u.containerType, u.n, u.mapped) + return oldV, false + } + cont := NewContainer() + cont.Update(u.containerType, u.n, u.mapped) + return cont, true +} + +// this struct is added to prevent the closure locals from being escaped out to the heap +type updater struct { + key uint64 + n int32 + containerType byte + mapped bool +} + +func (btc *bTreeContainers) PutContainerValues(key uint64, containerType byte, n int, mapped bool) { + a := updater{key, int32(n), containerType, mapped} + btc.tree.Put(key, a.update) +} + +func (btc *bTreeContainers) Remove(key uint64) { + btc.tree.Delete(key) +} + +func (btc *bTreeContainers) GetOrCreate(key uint64) *Container { + // Check the last* cache for same container. + if key == btc.lastKey && btc.lastContainer != nil { + return btc.lastContainer + } + + btc.lastKey = key + v, ok := btc.tree.Get(key) + if !ok { + cont := NewContainer() + btc.tree.Set(key, cont) + btc.lastContainer = cont + return cont + } + + btc.lastContainer = v + return btc.lastContainer +} + +func (btc *bTreeContainers) Count() (n uint64) { + e, _ := btc.tree.Seek(0) + _, c, err := e.Next() + for err != io.EOF { + n += uint64(c.N()) + _, c, err = e.Next() + } + return n +} + +func (btc *bTreeContainers) Clone() Containers { + nbtc := newBTreeContainers() + + itr, err := btc.tree.SeekFirst() + if err == io.EOF { + return nbtc + } + for { + k, v, err := itr.Next() + if err == io.EOF { + break + } + nbtc.tree.Set(k, v.Clone()) + } + return nbtc +} + +func (btc *bTreeContainers) Last() (key uint64, c *Container) { + if btc.tree.Len() == 0 { + return 0, nil + } + k, v := btc.tree.Last() + return k, v +} + +func (btc *bTreeContainers) Size() int { + return btc.tree.Len() +} + +func (btc *bTreeContainers) Reset() { + btc.tree = treeNew(cmp) + btc.lastKey = 0 + btc.lastContainer = nil +} + +func (btc *bTreeContainers) Iterator(key uint64) (citer ContainerIterator, found bool) { + e, ok := btc.tree.Seek(key) + if ok { + found = true + } + + return &btcIterator{ + e: e, + }, found +} + +func (btc *bTreeContainers) Repair() { + e, _ := btc.tree.Seek(0) + _, c, err := e.Next() + for err != io.EOF { + c.Repair() + _, c, err = e.Next() + } +} + +type btcIterator struct { + e *enumerator + key uint64 + val *Container +} + +func (i *btcIterator) Next() bool { + + k, v, err := i.e.Next() + if err == io.EOF { + return false + } + i.key = k + i.val = v + return true +} + +func (i *btcIterator) Value() (uint64, *Container) { + if i.val == nil { + return 0, nil + } + return i.key, i.val +} diff --git a/roaring/roaring.go b/roaring/roaring.go index d105c5b79..70dda25ec 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -139,7 +139,7 @@ func NewBitmap(a ...uint64) *Bitmap { // NewFileBitmap returns a Bitmap with an initial set of values, used for file storage. // By default, this is a copy of NewBitmap, but is replaced with B+Tree in server/enterprise.go -var NewFileBitmap func(a ...uint64) *Bitmap = NewBitmap +var NewFileBitmap func(a ...uint64) *Bitmap = NewBTreeBitmap // Clone returns a heap allocated copy of the bitmap. // Note: The OpWriter IS NOT copied to the new bitmap.