diff --git a/cache.go b/cache.go index 40509ab64..0bf4cd09d 100644 --- a/cache.go +++ b/cache.go @@ -452,9 +452,14 @@ func (s *simpleCache) Fetch(id uint64) (*Row, bool) { return m, ok } -// Add adds the bitmap to the cache, keyed on the id. +// Add adds the bitmap to the cache, keyed on the id. A nil row means +// deleting the row from the cache. func (s *simpleCache) Add(id uint64, b *Row) { - s.cache[id] = b + if b != nil { + s.cache[id] = b + } else { + delete(s.cache, id) + } } // nopCache represents a no-op Cache implementation. diff --git a/enterprise/b/btree.go b/enterprise/b/btree.go deleted file mode 100644 index 2fa5c24e8..000000000 --- a/enterprise/b/btree.go +++ /dev/null @@ -1,953 +0,0 @@ -// 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 b - -import ( - "io" - "sync" - - "github.com/pilosa/pilosa/roaring" -) - -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 *roaring.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 *roaring.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 *roaring.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 *roaring.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 *roaring.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 *roaring.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 *roaring.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 *roaring.Container, exists bool) (newV *roaring.Container, write bool)) (oldV *roaring.Container, written bool) { - pi := -1 - var p *x - q := t.r - var newV *roaring.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 *roaring.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 *roaring.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 *roaring.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/enterprise/b/containers_btree.go b/enterprise/b/containers_btree.go deleted file mode 100644 index eb7358a5d..000000000 --- a/enterprise/b/containers_btree.go +++ /dev/null @@ -1,219 +0,0 @@ -// 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 b - -import ( - "io" - - "github.com/pilosa/pilosa/roaring" -) - -func cmp(a, b uint64) int64 { - return int64(a - b) -} - -type bTreeContainers struct { - tree *tree - - lastKey uint64 - lastContainer *roaring.Container -} - -func newBTreeContainers() *bTreeContainers { - return &bTreeContainers{ - tree: treeNew(cmp), - } -} - -func NewBTreeBitmap(a ...uint64) *roaring.Bitmap { - b := &roaring.Bitmap{ - Containers: newBTreeContainers(), - } - // TODO: there's no way to report an error here - _, _ = b.Add(a...) - return b -} - -func (btc *bTreeContainers) Get(key uint64) *roaring.Container { - // Check the last* cache for same container. - if key == btc.lastKey && btc.lastContainer != nil { - return btc.lastContainer - } - - var c *roaring.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 *roaring.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 *roaring.Container, exists bool) (*roaring.Container, bool) { - // update the existing container - if exists { - oldV.Update(u.containerType, u.n, u.mapped) - return oldV, false - } - cont := roaring.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, typ byte, n int, mapped bool) { - a := updater{key, int32(n), typ, mapped} - btc.tree.Put(key, a.update) -} - -func (btc *bTreeContainers) Remove(key uint64) { - btc.tree.Delete(key) -} - -func (btc *bTreeContainers) GetOrCreate(key uint64) *roaring.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 := roaring.NewContainerArray(nil) - 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() roaring.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) First() (key uint64, c *roaring.Container) { - if btc.tree.Len() == 0 { - return 0, nil - } - return btc.tree.First() -} - -func (btc *bTreeContainers) Last() (key uint64, c *roaring.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 roaring.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 *roaring.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, *roaring.Container) { - if i.val == nil { - return 0, nil - } - return i.key, i.val -} diff --git a/enterprise/enterprise.go b/enterprise/enterprise.go index a75223801..f3a897551 100644 --- a/enterprise/enterprise.go +++ b/enterprise/enterprise.go @@ -21,13 +21,3 @@ // "ENTERPRISE=1 make install". These features were dual-licensed separately // from Pilosa community edition under the AGPL and Pilosa's commercial license. package enterprise - -import ( - "github.com/pilosa/pilosa/enterprise/b" - "github.com/pilosa/pilosa/roaring" -) - -func init() { // nolint: gochecknoinits - // Replace Bitmap constructor with B+Tree implementation - roaring.NewFileBitmap = b.NewBTreeBitmap -} diff --git a/fragment.go b/fragment.go index 87e7915b1..642f378bb 100644 --- a/fragment.go +++ b/fragment.go @@ -426,19 +426,18 @@ func (f *fragment) unprotectedRow(rowID uint64) *Row { func (f *fragment) rowFromStorage(rowID uint64) *Row { // Only use a subset of the containers. // NOTE: The start & end ranges must be divisible by container width. + // + // Note that OffsetRange now returns a new bitmap which uses frozen + // containers which will use copy-on-write semantics. The actual bitmap + // and Containers object are new and not shared, but the containers are + // shared. data := f.storage.OffsetRange(f.shard*ShardWidth, rowID*ShardWidth, (rowID+1)*ShardWidth) - // Reference bitmap subrange in storage. We Clone() data because otherwise - // row will contain pointers to containers in storage. This causes - // unexpected results when we cache the row and try to use it later. - // Basically, since we return the Row and release the fragment lock, the - // underlying fragment storage could be changed or snapshotted and thrown - // out at any point. row := &Row{ segments: []rowSegment{{ - data: *data.Clone(), + data: data, shard: f.shard, - writable: false, // this Row will probably be cached and shared, so it must be read only. + writable: true, }}, } row.invalidateCount() @@ -509,12 +508,15 @@ func (f *fragment) unprotectedSetBit(rowID, columnID uint64) (changed bool, err return false, errors.Wrap(err, "incrementing") } - // Get the row from row cache or fragment.storage. - row := f.unprotectedRow(rowID) - row.SetBit(columnID) - - // Update the cache. - f.cache.Add(rowID, row.Count()) + // If we're using a cache, update it. Otherwise skip the + // possibly-expensive count operation. + if f.CacheType != CacheTypeNone { + n := f.storage.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth) + f.cache.Add(rowID, n) + } + // Drop the rowCache entry; it's wrong, and we don't want to force + // a new copy if no one's reading it. + f.rowCache.Add(rowID, nil) f.stats.Count("setBit", 1, 0.001) @@ -574,12 +576,15 @@ func (f *fragment) unprotectedClearBit(rowID, columnID uint64) (changed bool, er return false, errors.Wrap(err, "incrementing") } - // Get the row from cache or fragment.storage. - row := f.unprotectedRow(rowID) - row.clearBit(columnID) - - // Update the cache. - f.cache.Add(rowID, row.Count()) + // If we're using a cache, update it. Otherwise skip the + // possibly-expensive count operation. + if f.CacheType != CacheTypeNone { + n := f.storage.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth) + f.cache.Add(rowID, n) + } + // Drop the rowCache entry; it's wrong, and we don't want to force + // a new copy if no one's reading it. + f.rowCache.Add(rowID, nil) f.stats.Count("clearBit", 1, 1.0) @@ -1857,9 +1862,7 @@ func (f *fragment) importPositions(set, clear []uint64, rowSet map[uint64]struct f.cache.BulkAdd(rowID, n) if smallWrite { - if _, ok := f.rowCache.Fetch(rowID); ok { // we won't update the rowCache if it wasn't already in there. - f.rowCache.Add(rowID, f.rowFromStorage(rowID)) - } + f.rowCache.Add(rowID, nil) } } diff --git a/fragment_internal_test.go b/fragment_internal_test.go index ecc506814..d815f41bd 100644 --- a/fragment_internal_test.go +++ b/fragment_internal_test.go @@ -34,6 +34,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pilosa/pilosa/pql" "github.com/pilosa/pilosa/roaring" + "github.com/pkg/errors" ) // Test flags @@ -3292,3 +3293,30 @@ func TestImportMultipleValues(t *testing.T) { } } } + +func TestFragmentConcurrentReadWrite(t *testing.T) { + f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + defer f.Clean(t) + + eg := &errgroup.Group{} + eg.Go(func() error { + for i := uint64(0); i < 1000; i++ { + _, err := f.setBit(i%4, i) + if err != nil { + return errors.Wrap(err, "setting bit") + } + } + return nil + }) + + acc := uint64(0) + for i := uint64(0); i < 100; i++ { + r := f.row(i % 4) + acc += r.Count() + } + if err := eg.Wait(); err != nil { + t.Errorf("error from setting a bit: %v", err) + } + + t.Logf("%d", acc) +} diff --git a/roaring/btree.go b/roaring/btree.go index 192993c3b..53489af89 100644 --- a/roaring/btree.go +++ b/roaring/btree.go @@ -570,6 +570,13 @@ func (t *tree) Set(k uint64, v *Container) { //defer func() { // dbg("--- POST\n%s\n====\n", t.dump()) //}() + // we don't want to store nil containers; if you try to set a + // container to nil, that's equivalent to not having one at that + // location. + if v == nil { + _ = t.Delete(k) + return + } pi := -1 var p *x @@ -642,7 +649,9 @@ func (t *tree) Put(k uint64, upd func(oldV *Container, exists bool) (newV *Conta if !written { return } - + if newV == nil { + return + } z := t.insert(btDPool.Get().(*d), 0, k, newV) t.r, t.first, t.last = z, z, z return @@ -667,7 +676,11 @@ func (t *tree) Put(k uint64, upd func(oldV *Container, exists bool) (newV *Conta if !written { return } - + // delete nil containers rather than storing them. + if newV == nil { + t.Delete(k) + return + } x.d[i].v = newV } return @@ -686,6 +699,10 @@ func (t *tree) Put(k uint64, upd func(oldV *Container, exists bool) (newV *Conta if !written { return } + // nil values don't need to exist, and break iteration later. + if newV == nil { + return + } switch { case x.c < 2*kd: @@ -876,6 +893,46 @@ func (e *enumerator) Next() (k uint64, v *Container, err error) { return k, v, err } +// Every iterates over a tree. +func (e *enumerator) Every(upd func(oldV *Container, exists bool) (newV *Container, write bool)) error { + if err := e.err; err != nil { + return err + } + + if e.ver != e.t.ver { + f, _ := e.t.Seek(e.k) + *e = *f + f.Close() + } + + for { + if e.q == nil { + e.err = io.EOF + return e.err + } + + if e.i >= e.q.c { + if err := e.next(); err != nil { + e.err = err + return e.err + } + } + + i := e.q.d[e.i] + nv, write := upd(i.v, true) + if write { + if nv == nil { + e.t.Delete(e.q.d[e.i].k) + } else { + e.q.d[e.i].v = nv + } + } + // Any error returned would be stashed in e.err, and would come up + // on the next call. + _ = e.next() + } +} + func (e *enumerator) next() error { if e.q == nil { e.err = io.EOF diff --git a/roaring/btree_test.go b/roaring/btree_test.go index c12892f62..422a7b767 100644 --- a/roaring/btree_test.go +++ b/roaring/btree_test.go @@ -467,13 +467,14 @@ func benchmarkSetRnd(b *testing.B, n int) { a[i] = rng.Next() } b.ResetTimer() + c := getDummyC(1) for i := 0; i < b.N; i++ { b.StopTimer() r := treeNew() debug.FreeOSMemory() b.StartTimer() for _, v := range a { - r.Set(uint64(v), nil) + r.Set(uint64(v), c) } b.StopTimer() r.Close() @@ -504,8 +505,9 @@ func benchmarkGetRnd(b *testing.B, n int) { for i := range a { a[i] = rng.Next() } + c := getDummyC(1) for _, v := range a { - r.Set(uint64(v), nil) + r.Set(uint64(v), c) } debug.FreeOSMemory() b.ResetTimer() @@ -1392,7 +1394,7 @@ func TestBtreePut(t *testing.T) { t.Fatal(iTest, g, e) } } - return nil, test.write + return getDummyC(99), test.write }) if test.exists { if g, e := oldV, getDummyC(test.oldV); g != e { @@ -1427,6 +1429,8 @@ func TestBtreePut(t *testing.T) { var e *Container if test.post[i+1] != -1 { e = getDummyC(test.post[i+1]) + } else { + e = getDummyC(99) } if g := v; g != e { t.Fatal(iTest, g, e) @@ -1445,7 +1449,7 @@ func TestBtreeSeek(t *testing.T) { tr := treeNew() for i := 0; i < N; i++ { k := 2*i + 1 - tr.Set(uint64(k), nil) + tr.Set(uint64(k), getDummyC(1)) } for i := 0; i < N; i++ { k := 2 * i @@ -1476,14 +1480,14 @@ func TestBtreePR4(t *testing.T) { tr := treeNew() for i := 0; i < 2*kd+1; i++ { k := 1000 * i - tr.Set(uint64(k), nil) + tr.Set(uint64(k), getDummyC(1)) } tr.Delete(1000 * kd) for i := 0; i < kd; i++ { - tr.Set(uint64(1000*(kd+1)-1-i), nil) + tr.Set(uint64(1000*(kd+1)-1-i), getDummyC(1)) } k := 1000*(kd+1) - 1 - kd - tr.Set(uint64(k), nil) + tr.Set(uint64(k), getDummyC(1)) if _, ok := tr.Get(uint64(k)); !ok { t.Fatalf("key lost: %v", k) } diff --git a/roaring/container_stash.go b/roaring/container_stash.go index 728730d64..46725238f 100644 --- a/roaring/container_stash.go +++ b/roaring/container_stash.go @@ -15,6 +15,7 @@ package roaring import ( + "fmt" "reflect" "runtime" "unsafe" @@ -39,24 +40,84 @@ type Container struct { pointer *uint16 // the data pointer len, cap int32 // length and cap n int32 // number of integers in container - mapped bool // mapped directly to a byte slice when true - typ byte // array, bitmap, or run + flags containerFlags // internal flags + typeID byte // array, bitmap, or run data [stashedArraySize]uint16 // immediate data for small arrays or runs } +type containerFlags uint8 + +const ( + flagMapped = containerFlags(1 << iota) + flagFrozen +) + +func (c *Container) String() string { + if c == nil { + return "" + } + froze := "" + switch c.flags { + case flagFrozen: + froze = "frozen " + case flagMapped: + froze = "mapped " + case flagFrozen | flagMapped: + froze = "frozen/mapped" + } + switch c.typeID { + case containerArray: + return fmt.Sprintf("<%sarray container, N=%d>", froze, c.N()) + case containerBitmap: + return fmt.Sprintf("<%sbitmap container, N=%d, len %dx uint64>", + froze, c.N(), len(c.bitmap())) + case containerRun: + return fmt.Sprintf("<%srun container, N=%d, len %dx interval>", + froze, c.N(), len(c.runs())) + default: + return fmt.Sprintf("", froze, c.typeID, c.N()) + } +} + // NewContainer returns a new instance of container. This trivial function // may later become more interesting. func NewContainer() *Container { statsHit("NewContainer") - c := &Container{typ: containerArray, len: 0, cap: stashedArraySize} + c := &Container{typeID: containerArray, len: 0, cap: stashedArraySize} c.pointer = (*uint16)(unsafe.Pointer(&c.data[0])) return c } // NewContainerBitmap makes a bitmap container using the provided bitmap, or // an empty one if provided bitmap is nil. If the provided bitmap is too short, -// it will be padded. -func NewContainerBitmap(n int32, bitmap []uint64) *Container { +// it will be padded. This function's API is wrong; it should have been +// written as NewContainerBitmapN, and this should not take the n argument, +// but I did it wrong initially and now that would be a breaking change. +func NewContainerBitmap(n int, bitmap []uint64) *Container { + if bitmap == nil { + return NewContainerBitmapN(nil, 0) + } + // pad to required length + if len(bitmap) < bitmapN { + bm2 := make([]uint64, bitmapN) + copy(bm2, bitmap) + bitmap = bm2 + } + c := &Container{typeID: containerBitmap} + c.setBitmap(bitmap) + // set n based on bitmap contents. + if n < 0 { + c.bitmapRepair() + } else { + c.setN(int32(n)) + } + return c +} + +// NewContainerBitmapN makes a bitmap container using the provided bitmap, or +// an empty one if provided bitmap is nil. If the provided bitmap is too short, +// it will be padded. The container's count is specified directly. +func NewContainerBitmapN(bitmap []uint64, n int32) *Container { if bitmap == nil { bitmap = make([]uint64, bitmapN) } @@ -66,23 +127,40 @@ func NewContainerBitmap(n int32, bitmap []uint64) *Container { copy(bm2, bitmap) bitmap = bm2 } - c := &Container{typ: containerBitmap, n: n} + c := &Container{typeID: containerBitmap, n: n} c.setBitmap(bitmap) return c } -// NewContainerArray returns an array using the provided set of values. It's -// okay if the slice is nil; that's a length of zero. +// NewContainerArray returns an array container using the provided set of +// values. It's okay if the slice is nil; that's a length of zero. func NewContainerArray(set []uint16) *Container { - c := &Container{typ: containerArray, n: int32(len(set))} + c := &Container{typeID: containerArray, n: int32(len(set))} c.setArray(set) return c } -// NewContainerRun creates a new run array using a provided (possibly nil) +// NewContainerArrayCopy returns an array container using the provided set of +// values. It's okay if the slice is nil; that's a length of zero. It copies +// the provided slice to new storage. +func NewContainerArrayCopy(set []uint16) *Container { + c := &Container{typeID: containerArray, n: int32(len(set))} + c.setArrayMaybeCopy(set, true) + return c +} + +// NewContainerArrayN returns an array container using the specified +// set of values, but overriding n. +func NewContainerArrayN(set []uint16, n int32) *Container { + c := &Container{typeID: containerArray, n: n} + c.setArray(set) + return c +} + +// NewContainerRun creates a new run container using a provided (possibly nil) // slice of intervals. func NewContainerRun(set []interval16) *Container { - c := &Container{typ: containerRun} + c := &Container{typeID: containerRun} c.setRuns(set) for _, run := range set { c.n += int32(run.last-run.start) + 1 @@ -90,61 +168,242 @@ func NewContainerRun(set []interval16) *Container { return c } +// NewContainerRunCopy creates a new run container using a provided (possibly nil) +// slice of intervals. It copies the provided slice to new storage. +func NewContainerRunCopy(set []interval16) *Container { + c := &Container{typeID: containerRun} + c.setRunsMaybeCopy(set, true) + for _, run := range set { + c.n += int32(run.last-run.start) + 1 + } + return c +} + +// NewContainerRunN creates a new run array using a provided (possibly nil) +// slice of intervals. It overrides n using the provided value. +func NewContainerRunN(set []interval16, n int32) *Container { + c := &Container{typeID: containerRun, n: n} + c.setRuns(set) + return c +} + // Mapped returns the internal mapped field, which indicates whether the // slice's backing store is believed to be associated with unwriteable // mmapped space. func (c *Container) Mapped() bool { - return c.mapped + if c == nil { + return false + } + return (c.flags & flagMapped) != 0 } -// N returns the internal n field. +// frozen() returns the internal frozen state. It isn't exported because +// nothing outside this package should be thinking about this. +func (c *Container) frozen() bool { + if c == nil { + return true + } + return (c.flags & flagFrozen) != 0 +} + +// N returns the 1-count of the container. func (c *Container) N() int32 { + if c == nil { + return 0 + } return c.n } +func (c *Container) setN(n int32) { + if c == nil { + if roaringParanoia { + panic("trying to setN on a nil container") + } + return + } + c.n = n +} + +func (c *Container) typ() byte { + if c == nil { + return containerNil + } + return c.typeID +} + +// setTyp should only be called if you already know that c is a +// non-nil, non-frozen, container. +func (c *Container) setTyp(newType byte) { + if roaringParanoia { + if c == nil || c.frozen() { + panic("setTyp on nil or frozen container") + } + } + c.typeID = newType +} + +func (c *Container) setMapped(mapped bool) { + if roaringParanoia { + if c == nil || c.frozen() { + panic("setMapped on nil or frozen container") + } + } + if mapped { + c.flags |= flagMapped + } else { + c.flags &^= flagMapped + } +} + +// Freeze returns an unmodifiable container identical to c. This might +// be c, now marked unmodifiable, or might be a new container. +func (c *Container) Freeze() *Container { + if c == nil { + return nil + } + c.flags |= flagFrozen + return c +} + +// Thaw returns a modifiable container identical to c. This may be c, or it +// may be a new container with distinct backing store. +func (c *Container) Thaw() *Container { + if roaringParanoia { + if c == nil { + panic("trying to thaw a nil container") + } + } + if c.flags&(flagFrozen|flagMapped) == 0 { + return c + } + return c.unmapOrClone() +} + +func (c *Container) unmapOrClone() *Container { + if c.flags&flagFrozen != 0 { + // Caqn't modify this container, therefore, we have to make a + // copy. + return c.Clone() + } + c.flags &^= flagMapped + // mapped: we want to unmap the storage. + switch c.typeID { + case containerArray: + // mapped flag is wrong here + if c.pointer == (*uint16)(unsafe.Pointer(&c.data)) { + return c + } + // maybe it fits in storage + if c.len <= stashedArraySize { + copy(c.data[:stashedArraySize], c.array()) + c.pointer, c.cap = (*uint16)(unsafe.Pointer(&c.data)), stashedArraySize + return c + } + array := c.array() + tmp := make([]uint16, c.len) + copy(tmp, array) + h := (*reflect.SliceHeader)(unsafe.Pointer(&tmp)) + c.pointer, c.cap = (*uint16)(unsafe.Pointer(h.Data)), int32(h.Cap) + runtime.KeepAlive(&tmp) + case containerRun: + // mapped flag is wrong here + if c.pointer == (*uint16)(unsafe.Pointer(&c.data)) { + return c + } + oldRuns := c.runs() + // maybe it fits in storage + if c.len <= stashedRunSize { + c.pointer, c.cap = (*uint16)(unsafe.Pointer(&c.data)), stashedRunSize + copy(c.runs(), oldRuns) + return c + } + tmp := make([]interval16, c.len) + copy(tmp, oldRuns) + h := (*reflect.SliceHeader)(unsafe.Pointer(&tmp)) + c.pointer, c.cap = (*uint16)(unsafe.Pointer(h.Data)), int32(h.Cap) + runtime.KeepAlive(&tmp) + case containerBitmap: + bitmap := c.bitmap() + tmp := make([]uint64, bitmapN) + copy(tmp, bitmap) + h := (*reflect.SliceHeader)(unsafe.Pointer(&tmp)) + c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(h.Data)), bitmapN, bitmapN + runtime.KeepAlive(&tmp) + default: + panic(fmt.Sprintf("can't thaw invalid container, type %d", c.typeID)) + } + return c +} + // array yields the data viewed as a slice of uint16 values. func (c *Container) array() []uint16 { if roaringParanoia { - if c.typ != containerArray { + if c == nil { + panic("attempt to read a nil container's array") + } + if c.typeID != containerArray { panic("attempt to read non-array's array") } } return *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{Data: uintptr(unsafe.Pointer(c.pointer)), Len: int(c.len), Cap: int(c.cap)})) } -// setArray stores a set of uint16s as data. -func (c *Container) setArray(array []uint16) { +// setArrayMaybeCopy stores a set of uint16s as data. c must not be frozen. +// If doCopy is set, it will ensure that the data get copied (possibly to +// its internal stash.) +func (c *Container) setArrayMaybeCopy(array []uint16, doCopy bool) { if roaringParanoia { - if c.typ != containerArray { + if c == nil || c.frozen() { + panic("setArray on nil or frozen container") + } + if c.typeID != containerArray { panic("attempt to write non-array's array") } } // no array: start with our default 5-value array if array == nil { c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(&c.data[0])), 0, stashedArraySize + c.n = c.len return } h := (*reflect.SliceHeader)(unsafe.Pointer(&array)) if h.Data == uintptr(unsafe.Pointer(c.pointer)) { // nothing to do but update length c.len = int32(h.Len) + c.n = c.len return } // array we can fit in data store: if len(array) <= stashedArraySize { copy(c.data[:stashedArraySize], array) c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(&c.data[0])), int32(len(array)), stashedArraySize - c.mapped = false // this is no longer using a hypothetical mmapped input array + c.n = c.len + c.flags &^= flagMapped // this is no longer using a hypothetical mmapped input array return } + // copy the array + if doCopy { + a2 := make([]uint16, len(array)) + copy(a2, array) + h = (*reflect.SliceHeader)(unsafe.Pointer(&a2)) + } c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(h.Data)), int32(h.Len), int32(h.Cap) + c.n = c.len runtime.KeepAlive(&array) } +// setArrayMaybeCopy stores a set of uint16s as data. c must not be frozen. +func (c *Container) setArray(array []uint16) { + c.setArrayMaybeCopy(array, false) +} + // bitmap yields the data viewed as a slice of uint64s holding bits. func (c *Container) bitmap() []uint64 { if roaringParanoia { - if c.typ != containerBitmap { + if c == nil { + panic("attempt to read nil container's bitmap") + } + if c.typeID != containerBitmap { panic("attempt to read non-bitmap's bitmap") } } @@ -153,8 +412,11 @@ func (c *Container) bitmap() []uint64 { // setBitmap stores a set of uint64s as data. func (c *Container) setBitmap(bitmap []uint64) { + if c == nil || c.frozen() { + panic("setBitmap on nil or frozen container") + } if roaringParanoia { - if c.typ != containerBitmap { + if c.typeID != containerBitmap { panic("attempt to write non-bitmap's bitmap") } } @@ -166,17 +428,29 @@ func (c *Container) setBitmap(bitmap []uint64) { // runs yields the data viewed as a slice of intervals. func (c *Container) runs() []interval16 { if roaringParanoia { - if c.typ != containerRun { + if c == nil { + panic("attempt to read nil container's runs") + } + if c.typeID != containerRun { panic("attempt to read non-run's runs") } } return *(*[]interval16)(unsafe.Pointer(&reflect.SliceHeader{Data: uintptr(unsafe.Pointer(c.pointer)), Len: int(c.len), Cap: int(c.cap)})) } -// setRuns stores a set of intervals as data. +// setRuns stores a set of intervals as data. c must not be frozen. func (c *Container) setRuns(runs []interval16) { + c.setRunsMaybeCopy(runs, false) +} + +// setRunsMaybeCopy stores a set of intervals as data. c must not be frozen. +// If doCopy is set, the values will be copied to different storage. +func (c *Container) setRunsMaybeCopy(runs []interval16, doCopy bool) { if roaringParanoia { - if c.typ != containerRun { + if c == nil || c.frozen() { + panic("setRuns on nil or frozen container") + } + if c.typeID != containerRun { panic("attempt to write non-run's runs") } } @@ -197,20 +471,64 @@ func (c *Container) setRuns(runs []interval16) { newRuns := *(*[]interval16)(unsafe.Pointer(&reflect.SliceHeader{Data: uintptr(unsafe.Pointer(&c.data[0])), Len: stashedRunSize, Cap: stashedRunSize})) copy(newRuns, runs) c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(&c.data[0])), int32(len(runs)), stashedRunSize - c.mapped = false // this is no longer using a hypothetical mmapped input array + c.flags &^= flagMapped // this is no longer using a hypothetical mmapped input array return } + if doCopy { + r2 := make([]interval16, len(runs)) + copy(r2, runs) + h = (*reflect.SliceHeader)(unsafe.Pointer(&r2)) + } c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(h.Data)), int32(h.Len), int32(h.Cap) runtime.KeepAlive(&runs) } -// Update updates the container -func (c *Container) Update(typ byte, n int32, mapped bool) { - c.typ = typ +// UpdateOrMake updates the container, yielding a new container if necessary. +func (c *Container) UpdateOrMake(typ byte, n int32, mapped bool) *Container { + if c == nil { + switch typ { + case containerRun: + c = NewContainerRunN(nil, n) + case containerBitmap: + c = NewContainerBitmapN(nil, n) + default: + c = NewContainerArrayN(nil, n) + } + c.flags |= flagMapped + return c + } + // ensure that we are allowed to modify this container + c = c.Thaw() + c.typeID = typ c.n = n - c.mapped = mapped + // note: this probably shouldn't be happening, the decision should be getting + // made when we specify the storage. + c.setMapped(mapped) // we don't know that any existing slice is usable, so let's ditch it - switch c.typ { + switch c.typeID { + case containerArray: + c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(&c.data[0])), int32(0), stashedArraySize + case containerRun: + c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(&c.data[0])), 0, stashedRunSize + default: + c.pointer, c.len, c.cap = nil, 0, 0 + } + return c +} + +// Update updates the container if possible. It is an error to +// call Update on a frozen container. +func (c *Container) Update(typ byte, n int32, mapped bool) { + if c == nil || c.frozen() { + panic("cannot Update a nil or frozen container") + } + c.typeID = typ + c.n = n + // note: this probably shouldn't be happening, the decision should be getting + // made when we specify the storage. + c.setMapped(mapped) + // we don't know that any existing slice is usable, so let's ditch it + switch c.typeID { case containerArray: c.pointer, c.len, c.cap = (*uint16)(unsafe.Pointer(&c.data[0])), int32(0), stashedArraySize case containerRun: @@ -222,56 +540,30 @@ func (c *Container) Update(typ byte, n int32, mapped bool) { // isArray returns true if the container is an array container. func (c *Container) isArray() bool { - return c.typ == containerArray + if roaringParanoia { + if c == nil { + panic("calling isArray on nil container") + } + } + return c.typeID == containerArray } // isBitmap returns true if the container is a bitmap container. func (c *Container) isBitmap() bool { - return c.typ == containerBitmap + if roaringParanoia { + if c == nil { + panic("calling isBitmap on nil container") + } + } + return c.typeID == containerBitmap } // isRun returns true if the container is a run-length-encoded container. func (c *Container) isRun() bool { - return c.typ == containerRun -} - -// unmapArray ensures that the container is not using mmapped storage. -func (c *Container) unmapArray() { - if !c.mapped { - return + if roaringParanoia { + if c == nil { + panic("calling isRun on nil container") + } } - array := c.array() - tmp := make([]uint16, c.len) - copy(tmp, array) - h := (*reflect.SliceHeader)(unsafe.Pointer(&tmp)) - c.pointer, c.cap = (*uint16)(unsafe.Pointer(h.Data)), int32(h.Cap) - runtime.KeepAlive(&tmp) - c.mapped = false -} - -// unmapBitmap ensures that the container is not using mmapped storage. -func (c *Container) unmapBitmap() { - if !c.mapped { - return - } - bitmap := c.bitmap() - tmp := make([]uint64, c.len) - copy(tmp, bitmap) - h := (*reflect.SliceHeader)(unsafe.Pointer(&tmp)) - c.pointer, c.cap = (*uint16)(unsafe.Pointer(h.Data)), int32(h.Cap) - runtime.KeepAlive(&tmp) - c.mapped = false -} - -// unmapRun ensures that the container is not using mmapped storage. -func (c *Container) unmapRun() { - if !c.mapped { - return - } - runs := c.runs() - tmp := make([]interval16, c.len) - copy(tmp, runs) - h := (*reflect.SliceHeader)(unsafe.Pointer(&tmp)) - c.pointer, c.cap = (*uint16)(unsafe.Pointer(h.Data)), int32(h.Cap) - c.mapped = false + return c.typeID == containerRun } diff --git a/roaring/containers_btree.go b/roaring/containers_btree.go index 11f282a83..2b1dd55a7 100644 --- a/roaring/containers_btree.go +++ b/roaring/containers_btree.go @@ -15,6 +15,7 @@ package roaring import ( + "fmt" "io" ) @@ -42,7 +43,7 @@ func NewBTreeBitmap(a ...uint64) *Bitmap { func (btc *bTreeContainers) Get(key uint64) *Container { // Check the last* cache for same container. - if key == btc.lastKey && btc.lastContainer != nil { + if key == btc.lastKey { return btc.lastContainer } @@ -57,11 +58,15 @@ func (btc *bTreeContainers) Get(key uint64) *Container { } func (btc *bTreeContainers) Put(key uint64, c *Container) { + // If we don't do this, a Put on a container we just got from + // Get can result in the tree containing a different container + // than we'll get on next lookup. + btc.lastKey, btc.lastContainer = key, c // 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.lastKey = ^uint64(0) } btc.tree.Set(key, c) } @@ -69,13 +74,13 @@ func (btc *bTreeContainers) Put(key uint64, c *Container) { func (u updater) update(oldV *Container, exists bool) (*Container, bool) { // update the existing container if exists { - oldV.Update(u.typ, u.n, u.mapped) - return oldV, false + oldV = oldV.UpdateOrMake(u.typ, u.n, u.mapped) + return oldV, true } cont := NewContainer() - cont.typ = u.typ - cont.n = u.n - cont.mapped = u.mapped + cont.setTyp(u.typ) + cont.setN(u.n) + cont.setMapped(u.mapped) return cont, true } @@ -98,7 +103,7 @@ func (btc *bTreeContainers) Remove(key uint64) { func (btc *bTreeContainers) GetOrCreate(key uint64) *Container { // Check the last* cache for same container. - if key == btc.lastKey && btc.lastContainer != nil { + if key == btc.lastKey { return btc.lastContainer } @@ -119,7 +124,7 @@ func (btc *bTreeContainers) Count() (n uint64) { e, _ := btc.tree.Seek(0) _, c, err := e.Next() for err != io.EOF { - n += uint64(c.n) + n += uint64(c.N()) _, c, err = e.Next() } return n @@ -142,6 +147,23 @@ func (btc *bTreeContainers) Clone() Containers { return nbtc } +func (btc *bTreeContainers) Freeze() 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.Freeze()) + } + return nbtc +} + func (btc *bTreeContainers) First() (key uint64, c *Container) { if btc.tree.Len() == 0 { return 0, nil @@ -163,7 +185,10 @@ func (btc *bTreeContainers) Size() int { func (btc *bTreeContainers) Reset() { btc.tree = treeNew() - btc.lastKey = 0 + // use a definitely-invalid key, so we can distinguish between "you + // just looked that up, and it was a nil container" and "you have + // never looked that up before." + btc.lastKey = ^uint64(0) btc.lastContainer = nil } @@ -187,6 +212,23 @@ func (btc *bTreeContainers) Repair() { } } +// Update calls fn (existing-container, existed), and expects +// (new-container, write). If write is true, the container is used to +// replace the given container. +func (btc *bTreeContainers) Update(key uint64, fn func(*Container, bool) (*Container, bool)) { + btc.tree.Put(key, fn) +} + +// UpdateEvery calls fn (existing-container, existed), and expects +// (new-container, write). If write is true, the container is used to +// replace the given container. +func (btc *bTreeContainers) UpdateEvery(fn func(*Container, bool) (*Container, bool)) { + e, _ := btc.tree.Seek(0) + // currently not handling the error from this, but in practice it has + // to be io.EOF. + _ = e.Every(fn) +} + type btcIterator struct { e *enumerator key uint64 @@ -194,19 +236,20 @@ type btcIterator struct { } func (i *btcIterator) Next() bool { - k, v, err := i.e.Next() if err == io.EOF { return false } + if roaringParanoia { + if v == nil { + panic(fmt.Sprintf("got nil container for key %d", k)) + } + } 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/containers.go b/roaring/containers_slice.go similarity index 64% rename from roaring/containers.go rename to roaring/containers_slice.go index 0cd515b9f..7a5722cf4 100644 --- a/roaring/containers.go +++ b/roaring/containers_slice.go @@ -50,15 +50,21 @@ func (sc *sliceContainers) PutContainerValues(key uint64, typ byte, n int, mappe i := search64(sc.keys, key) if i < 0 { c := NewContainer() - c.typ = typ - c.n = int32(n) - c.mapped = mapped + c.setTyp(typ) + c.setN(int32(n)) + c.setMapped(mapped) sc.insertAt(key, c, -i-1) } else { - c := sc.containers[i] - c.typ = typ - c.n = int32(n) - c.mapped = mapped + // if the container already exists, and is frozen, this may + // result in copying its data, which is sort of pointless + // because PutContainerValues almost always gets called + // because we're reading new data from a file -- but also + // that means this case probably never happens. + c := sc.containers[i].Thaw() + c.setTyp(typ) + c.setN(int32(n)) + c.setMapped(mapped) + sc.containers[i] = c } } @@ -114,6 +120,17 @@ func (sc *sliceContainers) Clone() Containers { return other } +func (sc *sliceContainers) Freeze() Containers { + other := newSliceContainers() + other.keys = make([]uint64, len(sc.keys)) + other.containers = make([]*Container, len(sc.containers)) + copy(other.keys, sc.keys) + for i, c := range sc.containers { + other.containers[i] = c.Freeze() + } + return other +} + func (sc *sliceContainers) First() (key uint64, c *Container) { if len(sc.keys) == 0 { return 0, nil @@ -136,7 +153,7 @@ func (sc *sliceContainers) Size() int { func (sc *sliceContainers) Count() uint64 { n := uint64(0) for i := range sc.containers { - n += uint64(sc.containers[i].n) + n += uint64(sc.containers[i].N()) } return n } @@ -169,6 +186,42 @@ func (sc *sliceContainers) Repair() { } } +// Update calls fn (existing-container, existed), and expects +// (new-container, write). If write is true, the container is used to +// replace the given container. +func (sc *sliceContainers) Update(key uint64, fn func(*Container, bool) (*Container, bool)) { + i, found := sc.seek(key) + var nc *Container + var write bool + if found { + nc, write = fn(sc.containers[i], true) + if write { + sc.containers[i] = nc + } + } else { + nc, write = fn(nil, false) + // don't expand the slice just to add a nil container, we + // could return that anyway + if write && nc != nil { + sc.containers = append(sc.containers, nil) + copy(sc.containers[i+1:], sc.containers[i:]) + sc.containers[i] = nc + } + } +} + +// UpdateEvery calls fn (existing-container, existed), and expects +// (new-container, write). If write is true, the container is used to +// replace the given container. +func (sc *sliceContainers) UpdateEvery(fn func(*Container, bool) (*Container, bool)) { + for i, c := range sc.containers { + nc, write := fn(c, true) + if write { + sc.containers[i] = nc + } + } +} + type sliceIterator struct { e *sliceContainers i int @@ -177,14 +230,20 @@ type sliceIterator struct { } func (si *sliceIterator) Next() bool { - if si.e == nil || si.i > len(si.e.keys)-1 { + if si.e == nil { return false } - si.key = si.e.keys[si.i] - si.value = si.e.containers[si.i] - si.i++ - - return true + // discard nil containers from iteration. we don't always + // actually remove them because copying is expensive. + for si.i < len(si.e.keys) { + si.key = si.e.keys[si.i] + si.value = si.e.containers[si.i] + si.i++ + if si.value != nil { + return true + } + } + return false } func (si *sliceIterator) Value() (uint64, *Container) { diff --git a/roaring/containers_test.go b/roaring/containers_test.go index 6d159b73d..9d5e1610d 100644 --- a/roaring/containers_test.go +++ b/roaring/containers_test.go @@ -44,14 +44,14 @@ func testContainersIterator(cs Containers, t *testing.T) { if !itr.Next() { t.Fatalf("one should be next, but got false") } - if key, val := itr.Value(); key != 1 || val.n != 1 { - t.Fatalf("Wrong k/v, exp: 1,1 got: %v,%v", key, val.n) + if key, val := itr.Value(); key != 1 || val.N() != 1 { + t.Fatalf("Wrong k/v, exp: 1,1 got: %v,%v", key, val.N()) } if !itr.Next() { t.Fatalf("two should be next, but got false") } - if key, val := itr.Value(); key != 2 || val.n != 2 { - t.Fatalf("Wrong k/v, exp: 2,2 got: %v,%v", key, val.n) + if key, val := itr.Value(); key != 2 || val.N() != 2 { + t.Fatalf("Wrong k/v, exp: 2,2 got: %v,%v", key, val.N()) } if itr.Next() { @@ -69,14 +69,14 @@ func testContainersIterator(cs Containers, t *testing.T) { if !found { t.Fatalf("should have found 3") } - if key, val := itr.Value(); key != 3 || val.n != 3 { - t.Fatalf("Wrong k/v, exp: 3,3 got: %v,%v", key, val.n) + if key, val := itr.Value(); key != 3 || val.N() != 3 { + t.Fatalf("Wrong k/v, exp: 3,3 got: %v,%v", key, val.N()) } if !itr.Next() { t.Fatalf("5 should be next, but got false") } - if key, val := itr.Value(); key != 5 || val.n != 5 { - t.Fatalf("Wrong k/v, exp: 5,5 got: %v,%v", key, val.n) + if key, val := itr.Value(); key != 5 || val.N() != 5 { + t.Fatalf("Wrong k/v, exp: 5,5 got: %v,%v", key, val.N()) } itr, found = cs.Iterator(4) @@ -86,14 +86,14 @@ func testContainersIterator(cs Containers, t *testing.T) { if !itr.Next() { t.Fatalf("5 should be next, but got false") } - if key, val := itr.Value(); key != 5 || val.n != 5 { - t.Fatalf("Wrong k/v, exp: 5,5 got: %v,%v", key, val.n) + if key, val := itr.Value(); key != 5 || val.N() != 5 { + t.Fatalf("Wrong k/v, exp: 5,5 got: %v,%v", key, val.N()) } if !itr.Next() { t.Fatalf("6 should be next, but got false") } - if key, val := itr.Value(); key != 6 || val.n != 6 { - t.Fatalf("Wrong k/v, exp: 6,6 got: %v,%v", key, val.n) + if key, val := itr.Value(); key != 6 || val.N() != 6 { + t.Fatalf("Wrong k/v, exp: 6,6 got: %v,%v", key, val.N()) } if itr.Next() { diff --git a/roaring/roaring.go b/roaring/roaring.go index 73b7e3dc6..7d71beb89 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -62,9 +62,10 @@ const ( ) const ( - containerArray byte = iota + 1 // slice of bit position values - containerBitmap // slice of 1024 uint64s - containerRun // container of run-encoded bits + containerNil byte = iota // no container + containerArray // slice of bit position values + containerBitmap // slice of 1024 uint64s + containerRun // container of run-encoded bits ) // map used for a more descriptive print @@ -74,6 +75,8 @@ var containerTypeNames = map[byte]string{ containerRun: "run", } +var fullContainer = NewContainerRun([]interval16{{start: 0, last: maxContainerVal}}).Freeze() + type Containers interface { // Get returns nil if the key does not exist. Get(key uint64) *Container @@ -95,6 +98,11 @@ type Containers interface { // Clone does a deep copy of Containers, including cloning all containers contained. Clone() Containers + // Freeze creates a shallow copy of Containers, freezing all the containers + // contained. The new copy is a distinct Containers, but the individual containers + // are shared (but marked as frozen). + Freeze() Containers + // First returns the lowest key and associated container. First() (key uint64, c *Container) @@ -104,6 +112,16 @@ type Containers interface { // Size returns the number of containers stored. Size() int + // Update calls fn (existing-container, existed), and expects + // (new-container, write). If write is true, the container is used to + // replace the given container. + Update(key uint64, fn func(*Container, bool) (*Container, bool)) + + // UpdateEvery calls fn (existing-container, existed), and expects + // (new-container, write). If write is true, the container is used to + // replace the given container. + UpdateEvery(fn func(*Container, bool) (*Container, bool)) + // Iterator returns a Contiterator which after a call to Next(), a call to Value() will // return the first container at or after key. found will be true if a // container is found at key. @@ -152,6 +170,21 @@ func NewBitmap(a ...uint64) *Bitmap { return b } +// NewSliceBitmap makes a new bitmap, explicitly selecting the slice containers +// type, which performs better in cases where we expect a contiguous block of +// containers added in ascending order, such as when extracting a range from +// another bitmap. +func NewSliceBitmap(a ...uint64) *Bitmap { + b := &Bitmap{ + Containers: newSliceContainers(), + } + // TODO: We have no way to report this. We aren't in a server context + // so we haven't got a logger, nothing is checking for nil returns + // from this... + _, _ = b.AddN(a...) + return b +} + // 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 = NewBTreeBitmap @@ -171,6 +204,23 @@ func (b *Bitmap) Clone() *Bitmap { return other } +// Freeze returns a shallow copy of the bitmap. The new bitmap +// is a distinct bitmap, with a new Containers object, but the +// actual containers it holds are the same as the parent's +// containers, but have been frozen. +func (b *Bitmap) Freeze() *Bitmap { + if b == nil { + return nil + } + + // Create a copy of the bitmap structure. + other := &Bitmap{ + Containers: b.Containers.Freeze(), + } + + return other +} + // Add adds values to the bitmap. TODO(2.0) deprecate - use the more general // AddN (though be aware that it modifies 'a' in place). func (b *Bitmap) Add(a ...uint64) (changed bool, err error) { @@ -238,7 +288,7 @@ func (b *Bitmap) DirectRemoveN(a ...uint64) (changed int) { // container level operation across a list of values and return the number of // trues while modifying the list of values in place to contain the // true-returning values in order. -func (b *Bitmap) directOpN(op func(c *Container, v uint16) bool, a ...uint64) (changed int) { +func (b *Bitmap) directOpN(op func(c *Container, v uint16) (*Container, bool), a ...uint64) (changed int) { hb := uint64(0xFFFFFFFFFFFFFFFF) // impossible sentinel value var cont *Container for _, v := range a { @@ -246,10 +296,15 @@ func (b *Bitmap) directOpN(op func(c *Container, v uint16) bool, a ...uint64) (c hb = newhb cont = b.Containers.GetOrCreate(hb) } - if op(cont, lowbits(v)) { + newC, change := op(cont, lowbits(v)) + if change { a[changed] = v changed++ } + if newC != cont { + b.Containers.Put(hb, newC) + cont = newC + } } return changed } @@ -258,7 +313,11 @@ func (b *Bitmap) directOpN(op func(c *Container, v uint16) bool, a ...uint64) (c // deprecate in favor of DirectAddN. func (b *Bitmap) DirectAdd(v uint64) bool { cont := b.Containers.GetOrCreate(highbits(v)) - return cont.add(lowbits(v)) + newC, changed := cont.add(lowbits(v)) + if newC != cont { + b.Containers.Put(highbits(v), newC) + } + return changed } // Contains returns true if v is in the bitmap. @@ -316,11 +375,11 @@ func (b *Bitmap) RemoveN(a ...uint64) (changed int, err error) { func (b *Bitmap) remove(v uint64) bool { c := b.Containers.Get(highbits(v)) - if c == nil { - return false + newC, changed := c.remove(lowbits(v)) + if newC != c { + b.Containers.Put(highbits(v), newC) } - // TODO - do nil check inside c.remove? - return c.remove(lowbits(v)) + return changed } // Min returns the lowest value in the bitmap. @@ -360,7 +419,7 @@ func (b *Bitmap) Any() bool { // container should be removed from the bitmap though. for iter.Next() { _, c := iter.Value() - if c.n > 0 { + if c.N() > 0 { return true } } @@ -374,7 +433,6 @@ func (b *Bitmap) Size() int { for citer.Next() { _, c := citer.Value() numbytes += c.size() - } return numbytes } @@ -407,7 +465,7 @@ func (b *Bitmap) CountRange(start, end uint64) (n uint64) { continue } if k < ekey { - n += uint64(c.n) + n += uint64(c.N()) continue } if k == ekey { @@ -463,6 +521,8 @@ func (b *Bitmap) ForEachRange(start, end uint64, fn func(uint64)) { } // OffsetRange returns a new bitmap with a containers offset by start. +// The containers themselves are shared, so they get frozen so it will +// be safe to interact with them. func (b *Bitmap) OffsetRange(offset, start, end uint64) *Bitmap { if lowbits(offset) != 0 { panic("offset must not contain low bits") @@ -477,13 +537,13 @@ func (b *Bitmap) OffsetRange(offset, start, end uint64) *Bitmap { off := highbits(offset) hi0, hi1 := highbits(start), highbits(end) citer, _ := b.Containers.Iterator(hi0) - other := NewBitmap() + other := NewSliceBitmap() for citer.Next() { k, c := citer.Value() if k >= hi1 { break } - other.Containers.Put(off+(k-hi0), c) + other.Containers.Put(off+(k-hi0), c.Freeze()) } return other } @@ -552,7 +612,10 @@ func (b *Bitmap) Union(others ...*Bitmap) *Bitmap { b.unionIntoTargetSingle(output, others[0]) return output } - output := b.Clone() + // It may seem counterintuitive to freeze this, but the result is + // a new bitmap which can be safely modified, but postponing any + // allocations until an actual write to any given container. + output := b.Freeze() output.UnionInPlace(others...) return output } @@ -571,11 +634,11 @@ func (b *Bitmap) unionIntoTargetSingle(target *Bitmap, other *Bitmap) { kj, cj := jiter.Value() for i || j { if i && (!j || ki < kj) { - target.Containers.Put(ki, ci.Clone()) + target.Containers.Put(ki, ci.Freeze()) i = iiter.Next() ki, ci = iiter.Value() } else if j && (!i || ki > kj) { - target.Containers.Put(kj, cj.Clone()) + target.Containers.Put(kj, cj.Freeze()) j = jiter.Next() kj, cj = jiter.Value() } else { // ki == kj @@ -708,11 +771,11 @@ func (b *Bitmap) unionInPlace(others ...*Bitmap) { tContainer := target.Containers.Get(iKey) // if the target's full, short-circuit out. if tContainer != nil { - if tContainer.n == maxContainerVal+1 { + if tContainer.N() == maxContainerVal+1 { bitmapIters.markItersWithKeyAsHandled(i, iKey) continue } - expectedN = int64(tContainer.n) + expectedN = int64(tContainer.N()) } // Check i and later iters for any max-range containers, and // find out how many there are. @@ -722,7 +785,7 @@ func (b *Bitmap) unionInPlace(others ...*Bitmap) { // range that a container can store, so instead of calculating a // union we can generate an RLE container that represents the entire // range. - tContainer = NewContainerRun([]interval16{{start: 0, last: maxContainerVal}}) + tContainer = fullContainer target.Containers.Put(iKey, tContainer) bitmapIters.markItersWithKeyAsHandled(i, iKey) continue @@ -737,9 +800,9 @@ func (b *Bitmap) unionInPlace(others ...*Bitmap) { // No existing target container. if summaryStats.c == 1 { // There's no target and we have only one container, we - // can just clone it instead of unioning. + // can just reuse it instead of unioning. statsHit("unionInPlace/reuse") - target.Containers.Put(iKey, iContainer.Clone()) + target.Containers.Put(iKey, iContainer.Freeze()) bitmapIters[i].handled = true continue } @@ -749,17 +812,19 @@ func (b *Bitmap) unionInPlace(others ...*Bitmap) { // first other container, but for some cases, that will // result in cloning a non-bitmap, then converting it // to a bitmap, and this will be expensive... - if expectedN >= 512 && iContainer.typ != containerBitmap { + if expectedN >= 512 && iContainer.typ() != containerBitmap { // copying the non-bitmap, then converting it, // is expensive. statsHit("unionInPlace/newBitmap") - tContainer = NewContainerBitmap(0, nil) + tContainer = NewContainerBitmapN(nil, 0) itersToUnion = bitmapIters[i:] } else { // either N will be small or iContainer is a // bitmap, so we can skip one union op by copying it. + // And we can just freeze it, and the copy will + // happen later if it's needed... statsHit("unionInPlace/clone") - tContainer = iContainer.Clone() + tContainer = iContainer.Freeze() itersToUnion = bitmapIters[i+1:] } } else { @@ -768,13 +833,13 @@ func (b *Bitmap) unionInPlace(others ...*Bitmap) { // convert it preemptively, because union into a // bitmap is nearly always faster. itersToUnion = bitmapIters[i:] - if expectedN >= 512 && tContainer.typ != containerBitmap { + if expectedN >= 512 && tContainer.typ() != containerBitmap { statsHit("unionInPlace/convertToBitmap") - switch tContainer.typ { + switch tContainer.typ() { case containerArray: - tContainer.arrayToBitmap() + tContainer = tContainer.arrayToBitmap() case containerRun: - tContainer.runToBitmap() + tContainer = tContainer.runToBitmap() } } } @@ -785,6 +850,7 @@ func (b *Bitmap) unionInPlace(others ...*Bitmap) { jKey, jContainer := iter.iter.Value() if iKey == jKey { + tContainer = tContainer.Thaw() tContainer.unionInPlace(jContainer) // "iter" is a local copy from the range // loop, not the actual slice member. @@ -821,7 +887,7 @@ func (b *Bitmap) Difference(other *Bitmap) *Bitmap { kj, cj := jiter.Value() for i || j { if i && (!j || ki < kj) { - output.Containers.Put(ki, ci.Clone()) + output.Containers.Put(ki, ci.Freeze()) i = iiter.Next() ki, ci = iiter.Value() } else if j && (!i || ki > kj) { @@ -848,11 +914,11 @@ func (b *Bitmap) Xor(other *Bitmap) *Bitmap { kj, cj := jiter.Value() for i || j { if i && (!j || ki < kj) { - output.Containers.Put(ki, ci.Clone()) + output.Containers.Put(ki, ci.Freeze()) i = iiter.Next() ki, ci = iiter.Value() } else if j && (!i || ki > kj) { - output.Containers.Put(kj, cj.Clone()) + output.Containers.Put(kj, cj.Freeze()) j = jiter.Next() kj, cj = jiter.Value() } else { // ki == kj @@ -880,7 +946,7 @@ func (b *Bitmap) Shift(n int) (*Bitmap, error) { if lastCarry { o.add(0) } - if o.n > 0 { + if o.N() > 0 { output.Containers.Put(ki, o) } lastCarry = carry @@ -901,7 +967,7 @@ func (b *Bitmap) removeEmptyContainers() { citer, _ := b.Containers.Iterator(0) for citer.Next() { k, c := citer.Value() - if c.n == 0 { + if c.N() == 0 { b.Containers.Remove(k) } } @@ -911,7 +977,7 @@ func (b *Bitmap) countEmptyContainers() int { citer, _ := b.Containers.Iterator(0) for citer.Next() { _, c := citer.Value() - if c.n == 0 { + if c.N() == 0 { result++ } } @@ -1002,10 +1068,10 @@ func (b *Bitmap) writeToUnoptimized(w io.Writer) (n int64, err error) { // TODO: instead of commenting this out, we need to make it a configuration option //count := c.count() //assert(c.count() == c.n, "cannot write container count, mismatch: count=%d, n=%d", count, c.n) - if c.n > 0 { + if c.N() > 0 { ew.WriteUint64(byte8, key) - ew.WriteUint16(byte2, uint16(c.typ)) - ew.WriteUint16(byte2, uint16(c.n-1)) + ew.WriteUint16(byte2, uint16(c.typ())) + ew.WriteUint16(byte2, uint16(c.N()-1)) } } @@ -1016,7 +1082,7 @@ func (b *Bitmap) writeToUnoptimized(w io.Writer) (n int64, err error) { citer, _ = b.Containers.Iterator(0) for citer.Next() { _, c := citer.Value() - if c.n > 0 { + if c.N() > 0 { ew.WriteUint32(byte4, offset) offset += uint32(c.size()) } @@ -1032,7 +1098,7 @@ func (b *Bitmap) writeToUnoptimized(w io.Writer) (n int64, err error) { citer, _ = b.Containers.Iterator(0) for citer.Next() { _, c := citer.Value() - if c.n > 0 { + if c.N() > 0 { nn, err := c.WriteTo(w) n += nn if err != nil { @@ -1089,13 +1155,17 @@ func (b *Bitmap) unmarshalPilosaRoaring(data []byte) error { // Map byte slice directly to the container data. citer.Next() _, c := citer.Value() - switch c.typ { + // this shouldn't happen, since we don't normally store nils. + if c == nil { + continue + } + switch c.typ() { case containerRun: runCount := binary.LittleEndian.Uint16(data[offset : offset+runCountHeaderSize]) c.setRuns((*[0xFFFFFFF]interval16)(unsafe.Pointer(&data[offset+runCountHeaderSize]))[:runCount:runCount]) opsOffset = int(offset) + runCountHeaderSize + len(c.runs())*interval16Size case containerArray: - c.setArray((*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[offset]))[:c.n:c.n]) + c.setArray((*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[offset]))[:c.N():c.N()]) opsOffset = int(offset) + len(c.array())*2 // sizeof(uint32) case containerBitmap: c.setBitmap((*[0xFFFFFFF]uint64)(unsafe.Pointer(&data[offset]))[:bitmapN:bitmapN]) @@ -1243,6 +1313,11 @@ func (itr *Iterator) Seek(seek uint64) { return // eof } itr.key, itr.c = itr.citer.Value() + if roaringParanoia { + if itr.c == nil { + panic("seeking iterator got a nil container when Next() was true") + } + } // Move to the correct value index inside the container. lb := lowbits(seek) @@ -1298,7 +1373,7 @@ func (itr *Iterator) Next() (v uint64, eof bool) { // Iterate over containers until we find the next value or EOF. for { if itr.c.isArray() { - if itr.j >= itr.c.n-1 { + if itr.j >= itr.c.N()-1 { // Reached end of array, move to the next container. if !itr.citer.Next() { itr.c = nil @@ -1434,6 +1509,9 @@ func (c *Container) count() (n int32) { // countRange counts the number of bits set between [start, end). func (c *Container) countRange(start, end int32) (n int32) { + if c == nil { + return 0 + } if c.isArray() { return c.arrayCountRange(start, end) } else if c.isRun() { @@ -1518,73 +1596,78 @@ func (c *Container) runCountRange(start, end int32) (n int32) { } // add adds a value to the container. -func (c *Container) add(v uint16) (added bool) { - +func (c *Container) add(v uint16) (newC *Container, added bool) { + if c == nil { + return NewContainerArray([]uint16{v}), true + } if c.isArray() { - added = c.arrayAdd(v) + return c.arrayAdd(v) } else if c.isRun() { - added = c.runAdd(v) + return c.runAdd(v) } else { - added = c.bitmapAdd(v) + return c.bitmapAdd(v) } - if added { - c.n++ - } - return added } -func (c *Container) arrayAdd(v uint16) bool { +func (c *Container) arrayAdd(v uint16) (*Container, bool) { // Optimize appending to the end of an array container. array := c.array() - if c.n > 0 && c.n < ArrayMaxSize && c.isArray() && array[c.n-1] < v { + if c.N() > 0 && c.N() < ArrayMaxSize && c.isArray() && array[c.N()-1] < v { statsHit("arrayAdd/append") - c.unmapArray() + c = c.Thaw() array = append(c.array(), v) c.setArray(array) - return true + return c, true } // Find index of the integer in the container. Exit if it already exists. i := search32(array, v) if i >= 0 { - return false + return c, false } // Convert to a bitmap container if too many values are in an array container. - if c.n >= ArrayMaxSize { + if c.N() >= ArrayMaxSize { statsHit("arrayAdd/arrayToBitmap") - c.arrayToBitmap() + c = c.arrayToBitmap() return c.bitmapAdd(v) } // Otherwise insert into array. statsHit("arrayAdd/insert") - c.unmapArray() + c = c.Thaw() i = -i - 1 array = append(c.array(), 0) copy(array[i+1:], array[i:]) array[i] = v c.setArray(array) - return true + return c, true } -func (c *Container) bitmapAdd(v uint16) bool { - if c.bitmapContains(v) { - return false +func (c *Container) bitmapAdd(v uint16) (*Container, bool) { + if c == nil { + c = NewContainerBitmapN(nil, 1) + c.bitmap()[v/64] |= (1 << uint64(v%64)) + return c, true } - c.unmapBitmap() + if c.bitmapContains(v) { + return c, false + } + c = c.Thaw() c.bitmap()[v/64] |= (1 << uint64(v%64)) - return true + c.setN(c.N() + 1) + return c, true } -func (c *Container) runAdd(v uint16) bool { +func (c *Container) runAdd(v uint16) (*Container, bool) { runs := c.runs() if len(runs) == 0 { - c.unmapRun() + c = c.Thaw() c.setRuns([]interval16{{start: v, last: v}}) - return true + c.setN(1) + return c, true } i := sort.Search(len(runs), @@ -1596,10 +1679,10 @@ func (c *Container) runAdd(v uint16) bool { iv := runs[i] if v >= iv.start && iv.last >= v { - return false + return c, false } - c.unmapRun() + c = c.Thaw() runs = c.runs() if iv.last < v { if iv.last == v-1 { @@ -1613,7 +1696,8 @@ func (c *Container) runAdd(v uint16) bool { runs[i-1].last = iv.last runs = append(runs[:i], runs[i+1:]...) c.setRuns(runs) - return true + c.setN(c.N() + 1) + return c, true } // just before an interval runs[i].start-- @@ -1626,11 +1710,15 @@ func (c *Container) runAdd(v uint16) bool { runs = append(runs[:i], append([]interval16{newIv}, runs[i:]...)...) } c.setRuns(runs) - return true + c.setN(c.N() + 1) + return c, true } // Contains returns true if v is in the container. func (c *Container) Contains(v uint16) bool { + if c == nil { + return false + } if c.isArray() { return c.arrayContains(v) } else if c.isRun() { @@ -1684,17 +1772,17 @@ func (c *Container) countRuns() (r int32) { // optimize converts the container to the type which will take up the least // amount of space. -func (c *Container) optimize() { - if c.n == 0 { +func (c *Container) optimize() *Container { + if c.N() == 0 { statsHit("optimize/empty") - return + return nil } runs := c.countRuns() var newType byte - if runs <= runMaxSize && runs <= c.n/2 { + if runs <= runMaxSize && runs <= c.N()/2 { newType = containerRun - } else if c.n < ArrayMaxSize { + } else if c.N() < ArrayMaxSize { newType = containerArray } else { newType = containerBitmap @@ -1704,75 +1792,94 @@ func (c *Container) optimize() { if c.isArray() { if newType == containerBitmap { statsHit("optimize/arrayToBitmap") - c.arrayToBitmap() + c = c.arrayToBitmap() } else if newType == containerRun { statsHit("optimize/arrayToRun") - c.arrayToRun(runs) + c = c.arrayToRun(runs) } else { statsHit("optimize/arrayUnchanged") } } else if c.isBitmap() { if newType == containerArray { statsHit("optimize/bitmapToArray") - c.bitmapToArray() + c = c.bitmapToArray() } else if newType == containerRun { statsHit("optimize/bitmapToRun") - c.bitmapToRun(runs) + c = c.bitmapToRun(runs) } else { statsHit("optimize/bitmapUnchanged") } } else if c.isRun() { if newType == containerBitmap { statsHit("optimize/runToBitmap") - c.runToBitmap() + c = c.runToBitmap() } else if newType == containerArray { statsHit("optimize/runToArray") - c.runToArray() + c = c.runToArray() } else { statsHit("optimize/runUnchanged") } } + return c } // unionInPlace does not necessarily preserve container's N; it's expected // to be used when running a sequence of unions, after which you should // call Repair(). (As of this writing, that only matters for bitmaps.) -func (c *Container) unionInPlace(other *Container) { - switch c.typ { +// +// If called on a frozen container, or a container of the wrong sort, +// it is possible that the returned container will not actually be the +// original container; in-place is a suggestion. +func (c *Container) unionInPlace(other *Container) *Container { + if c == nil { + return other.Freeze() + } + if other == nil { + return c + } + // short-circuit the trivial cases + if c.N() == maxContainerVal+1 || other.N() == maxContainerVal+1 { + return fullContainer + } + switch c.typ() { case containerBitmap: - switch other.typ { + switch other.typ() { case containerBitmap: - unionBitmapBitmapInPlace(c, other) + return unionBitmapBitmapInPlace(c, other) case containerArray: - unionBitmapArrayInPlace(c, other) + return unionBitmapArrayInPlace(c, other) case containerRun: - unionBitmapRunInPlace(c, other) + return unionBitmapRunInPlace(c, other) } case containerArray: - switch other.typ { + switch other.typ() { case containerBitmap: - c.arrayToBitmap() - unionBitmapBitmapInPlace(c, other) + c = c.arrayToBitmap() + return unionBitmapBitmapInPlace(c, other) case containerArray: - unionArrayArrayInPlace(c, other) + return unionArrayArrayInPlace(c, other) case containerRun: - c.arrayToBitmap() - unionBitmapRunInPlace(c, other) + c = c.arrayToBitmap() + return unionBitmapRunInPlace(c, other) } case containerRun: - switch other.typ { + switch other.typ() { case containerBitmap: - c.runToBitmap() - unionBitmapBitmapInPlace(c, other) + c = c.runToBitmap() + return unionBitmapBitmapInPlace(c, other) case containerArray: - c.runToBitmap() - unionBitmapArrayInPlace(c, other) + c = c.runToBitmap() + return unionBitmapArrayInPlace(c, other) case containerRun: - c.runToBitmap() - unionBitmapRunInPlace(c, other) + c = c.runToBitmap() + return unionBitmapRunInPlace(c, other) } } + if roaringParanoia { + panic(fmt.Sprintf("invalid union op: unknown types %d/%d", c.typ(), other.typ())) + } + return c } func (c *Container) arrayContains(v uint16) bool { @@ -1803,58 +1910,71 @@ func (c *Container) runContains(v uint16) bool { } // remove removes a value from the container. -func (c *Container) remove(v uint16) (removed bool) { - if c.isArray() { - removed = c.arrayRemove(v) - } else if c.isRun() { - removed = c.runRemove(v) - } else { - removed = c.bitmapRemove(v) +func (c *Container) remove(v uint16) (newC *Container, removed bool) { + if c == nil { + return nil, false + } + if c.isArray() { + return c.arrayRemove(v) + } else if c.isRun() { + return c.runRemove(v) + } else { + return c.bitmapRemove(v) } - return removed } -func (c *Container) arrayRemove(v uint16) bool { +func (c *Container) arrayRemove(v uint16) (*Container, bool) { array := c.array() i := search32(array, v) if i < 0 { - return false + return c, false } - c.unmapArray() + // removing the last item? we can just return the empty container. + if c.N() == 1 { + return nil, true + } + c = c.Thaw() array = c.array() array = append(array[:i], array[i+1:]...) - c.n-- c.setArray(array) - return true + return c, true } -func (c *Container) bitmapRemove(v uint16) bool { +func (c *Container) bitmapRemove(v uint16) (*Container, bool) { if !c.bitmapContains(v) { - return false + return c, false } - c.unmapBitmap() + // removing the last item? we can just return the empty container. + if c.N() == 1 { + return nil, true + } + c = c.Thaw() // Lower count and remove element. c.bitmap()[v/64] &^= (uint64(1) << uint(v%64)) - c.n-- + c.setN(c.N() - 1) // Convert to array if we go below the threshold. - if c.n == ArrayMaxSize { + if c.N() == ArrayMaxSize { statsHit("bitmapRemove/bitmapToArray") - c.bitmapToArray() + c = c.bitmapToArray() } - return true + return c, true } // runRemove removes v from a run container, and returns true if v was removed. -func (c *Container) runRemove(v uint16) bool { +func (c *Container) runRemove(v uint16) (*Container, bool) { runs := c.runs() i, contains := binSearchRuns(v, runs) if !contains { - return false + return c, false } - c.unmapRun() + // removing the last item? we can just return the empty container. + if c.N() == 1 { + return nil, true + } + c = c.Thaw() runs = c.runs() if v == runs[i].last && v == runs[i].start { runs = append(runs[:i], runs[i+1:]...) @@ -1870,9 +1990,9 @@ func (c *Container) runRemove(v uint16) bool { runs[i+1] = interval16{start: v + 1, last: last} // runs = append(runs[:i+1], append([]interval16{{start: v + 1, last: last}}, runs[i+1:]...)...) } - c.n-- + c.setN(c.N() - 1) c.setRuns(runs) - return true + return c, true } // min returns the minimum value in the container. @@ -1888,6 +2008,10 @@ func (c *Container) min() (uint16, bool) { // max returns the maximum value in the container. func (c *Container) max() uint16 { + if c == nil || c.N() == 0 { + // probably wrong, but prevents a crash elsewhere + return 0 + } if c.isArray() { return c.arrayMax() } else if c.isRun() { @@ -1908,9 +2032,6 @@ func (c *Container) arrayMin() (uint16, bool) { func (c *Container) arrayMax() uint16 { array := c.array() - if len(array) == 0 { - return 0 // probably hiding some ugly bug but it prevents a crash - } return array[len(array)-1] } @@ -1960,26 +2081,33 @@ func (c *Container) runMax() uint16 { } // bitmapToArray converts from bitmap format to array format. -func (c *Container) bitmapToArray() { +func (c *Container) bitmapToArray() *Container { statsHit("bitmapToArray") - bitmap := c.bitmap() - c.setBitmap(nil) - c.typ = containerArray - c.mapped = false - - // return early if empty - if c.n == 0 { - c.setArray(nil) - return + if c == nil { + if roaringParanoia { + panic("nil container for bitmapToArray") + } + return nil } + // If c is frozen, we'll be making a new array container. Otherwise, + // we'll convert this container. + if c.N() == 0 { + if c.frozen() { + return NewContainerArray(nil) + } + c.setTyp(containerArray) + c.setArray(nil) + return c + } + bitmap := c.bitmap() n := int32(0) - array := make([]uint16, c.n) + array := make([]uint16, c.N()) for i, word := range bitmap { for word != 0 { t := word & -word if roaringParanoia { - if n >= c.n { + if n >= c.N() { panic("bitmap has more bits set than container.n") } } @@ -1989,71 +2117,112 @@ func (c *Container) bitmapToArray() { } } if roaringParanoia { - if n != c.n { + if n != c.N() { panic("bitmap has fewer bits set than container.n") } } + if c.frozen() { + return NewContainerArray(array) + } + c.setTyp(containerArray) + c.setMapped(false) c.setArray(array) + return c } // arrayToBitmap converts from array format to bitmap format. -func (c *Container) arrayToBitmap() { +func (c *Container) arrayToBitmap() *Container { statsHit("arrayToBitmap") - array := c.array() - c.typ = containerBitmap - bitmap := make([]uint64, bitmapN) - c.setBitmap(bitmap) - c.mapped = false + if c == nil { + if roaringParanoia { + panic("nil container for arrayToBitmap") + } + return nil + } // return early if empty - if c.n == 0 { - return + if c.N() == 0 { + if c.frozen() { + return NewContainerBitmap(0, nil) + } + c.setTyp(containerBitmap) + c.setBitmap(make([]uint64, bitmapN)) + return c } - for _, v := range array { + bitmap := make([]uint64, bitmapN) + for _, v := range c.array() { bitmap[int(v)/64] |= (uint64(1) << uint(v%64)) } + if c.frozen() { + return NewContainerBitmapN(bitmap, c.N()) + } + c.setTyp(containerBitmap) + c.setMapped(false) + c.setBitmap(bitmap) + return c } // runToBitmap converts from RLE format to bitmap format. -func (c *Container) runToBitmap() { +func (c *Container) runToBitmap() *Container { statsHit("runToBitmap") - runs := c.runs() - bitmap := make([]uint64, bitmapN) - c.typ = containerBitmap - c.setBitmap(bitmap) - - c.mapped = false - - // return early if empty - if c.n == 0 { - return + if c == nil { + if roaringParanoia { + panic("nil container for runToBitmap") + } + return nil } - for _, r := range runs { + // return early if empty + if c.N() == 0 { + if c.frozen() { + return NewContainerBitmap(0, nil) + } + c.setTyp(containerBitmap) + c.setBitmap(make([]uint64, bitmapN)) + return c + } + bitmap := make([]uint64, bitmapN) + for _, r := range c.runs() { // TODO this can be ~64x faster for long runs by setting maxBitmap instead of single bits //note v must be int or will overflow for v := int(r.start); v <= int(r.last); v++ { bitmap[v/64] |= (uint64(1) << uint(v%64)) } } + if c.frozen() { + return NewContainerBitmapN(bitmap, c.N()) + } + c.setTyp(containerBitmap) + c.setMapped(false) + c.setBitmap(bitmap) + return c } // bitmapToRun converts from bitmap format to RLE format. -func (c *Container) bitmapToRun(numRuns int32) { +func (c *Container) bitmapToRun(numRuns int32) *Container { statsHit("bitmapToRun") - bitmap := c.bitmap() - c.mapped = false - c.typ = containerRun - // return early if empty - if c.n == 0 { - c.setRuns(nil) - return + if c == nil { + if roaringParanoia { + panic("nil container for bitmapToRun") + } + return nil } + + // return early if empty + if c.N() == 0 { + if c.frozen() { + return NewContainerRun(nil) + } + c.setTyp(containerRun) + c.setRuns(nil) + return c + } + + bitmap := c.bitmap() if numRuns == 0 { numRuns = bitmapCountRuns(bitmap) } - runs := make([]interval16, 0, numRuns) current := bitmap[0] @@ -2093,20 +2262,37 @@ func (c *Container) bitmapToRun(numRuns int32) { // pad LSBs with 0s current = current & (current + 1) } + if c.frozen() { + return NewContainerRunN(runs, c.N()) + } + c.setTyp(containerRun) c.setRuns(runs) + c.setMapped(false) + return c } // arrayToRun converts from array format to RLE format. -func (c *Container) arrayToRun(numRuns int32) { +func (c *Container) arrayToRun(numRuns int32) *Container { statsHit("arrayToRun") - array := c.array() - c.typ = containerRun - c.mapped = false - // return early if empty - if c.n == 0 { - c.setRuns(nil) - return + if c == nil { + if roaringParanoia { + panic("nil container for arrayToRun") + } + return nil } + + // return early if empty + if c.N() == 0 { + if c.frozen() { + return NewContainerRun(nil) + } + c.setTyp(containerRun) + c.setRuns(nil) + return c + } + + array := c.array() + if numRuns == 0 { numRuns = arrayCountRuns(array) } @@ -2121,24 +2307,39 @@ func (c *Container) arrayToRun(numRuns int32) { } } // append final run - runs = append(runs, interval16{start, array[c.n-1]}) + runs = append(runs, interval16{start, array[c.N()-1]}) + if c.frozen() { + return NewContainerRunN(runs, c.N()) + } + c.setTyp(containerRun) + c.setMapped(false) c.setRuns(runs) + return c } // runToArray converts from RLE format to array format. -func (c *Container) runToArray() { +func (c *Container) runToArray() *Container { statsHit("runToArray") - runs := c.runs() - c.typ = containerArray - c.mapped = false - - // return early if empty - if c.n == 0 { - c.setArray(nil) - return + if c == nil { + if roaringParanoia { + panic("nil container for runToArray") + } + return nil } - array := make([]uint16, c.n) + // return early if empty + if c.N() == 0 { + if c.frozen() { + return NewContainerArray(nil) + } + c.setTyp(containerArray) + c.setArray(nil) + return c + } + + runs := c.runs() + + array := make([]uint16, c.N()) n := int32(0) for _, r := range runs { for v := int(r.start); v <= int(r.last); v++ { @@ -2147,38 +2348,43 @@ func (c *Container) runToArray() { } } if roaringParanoia { - if n != c.n { + if n != c.N() { panic("run has fewer bits set than container.n") } } + if c.frozen() { + return NewContainerArray(array) + } + c.setTyp(containerArray) + c.setMapped(false) c.setArray(array) + return c } // Clone returns a copy of c. func (c *Container) Clone() (out *Container) { statsHit("Container/Clone") - switch c.typ { + if c == nil { + return nil + } + switch c.typ() { case containerArray: statsHit("Container/Clone/Array") - cArray := c.array() - array := make([]uint16, len(cArray)) - copy(array, cArray) - out = NewContainerArray(array) + out = NewContainerArrayCopy(c.array()) case containerBitmap: statsHit("Container/Clone/Bitmap") - other := NewContainerBitmap(c.n, nil) + other := NewContainerBitmapN(nil, c.N()) copy(other.bitmap(), c.bitmap()) out = other case containerRun: statsHit("Container/Clone/Run") - cRuns := c.runs() - runs := make([]interval16, len(cRuns)) - copy(runs, cRuns) - out = NewContainerRun(runs) + out = NewContainerRunCopy(c.runs()) + default: + panic(fmt.Sprintf("cloning a container of unknown type %d", c.typ())) } // this should probably never happen if roaringParanoia { - if out.n != out.count() { + if out.N() != out.count() { panic("cloned container has wrong n") } } @@ -2187,6 +2393,9 @@ func (c *Container) Clone() (out *Container) { // WriteTo writes c to w. func (c *Container) WriteTo(w io.Writer) (n int64, err error) { + if c == nil { + return 0, nil + } if c.isArray() { return c.arrayWriteTo(w) } else if c.isRun() { @@ -2210,7 +2419,7 @@ func (c *Container) arrayWriteTo(w io.Writer) (n int64, err error) { //} // Write sizeof(uint16) * cardinality bytes. - nn, err := w.Write((*[0xFFFFFFF]byte)(unsafe.Pointer(&array[0]))[: 2*c.n : 2*c.n]) + nn, err := w.Write((*[0xFFFFFFF]byte)(unsafe.Pointer(&array[0]))[: 2*c.N() : 2*c.N()]) return int64(nn), err } @@ -2251,7 +2460,12 @@ func (c *Container) size() int { // info returns the current stats about the container. func (c *Container) info() containerInfo { - info := containerInfo{N: c.n} + info := containerInfo{N: c.N()} + if c == nil { + info.Type = "nil" + info.Alloc = 0 + return info + } if c.isArray() { info.Type = "array" @@ -2264,7 +2478,7 @@ func (c *Container) info() containerInfo { info.Alloc = len(c.bitmap()) * 8 // sizeof(uint64) } - if c.mapped { + if c.Mapped() { if c.isArray() { info.Pointer = unsafe.Pointer(&c.array()[0]) } else if c.isRun() { @@ -2281,24 +2495,27 @@ func (c *Container) info() containerInfo { func (c *Container) check() error { var a ErrorList + if c == nil { + return nil + } if c.isArray() { array := c.array() - if int32(len(array)) != c.n { - a.Append(fmt.Errorf("array count mismatch: count=%d, n=%d", len(array), c.n)) + if int32(len(array)) != c.N() { + a.Append(fmt.Errorf("array count mismatch: count=%d, n=%d", len(array), c.N())) } } else if c.isRun() { n := c.runCountRange(0, maxContainerVal+1) - if n != c.n { - a.Append(fmt.Errorf("run count mismatch: count=%d, n=%d", n, c.n)) + if n != c.N() { + a.Append(fmt.Errorf("run count mismatch: count=%d, n=%d", n, c.N())) } } else if c.isBitmap() { - if n := c.bitmapCountRange(0, maxContainerVal+1); n != c.n { - a.Append(fmt.Errorf("bitmap count mismatch: count=%d, n=%d", n, c.n)) + if n := c.bitmapCountRange(0, maxContainerVal+1); n != c.N() { + a.Append(fmt.Errorf("bitmap count mismatch: count=%d, n=%d", n, c.N())) } } else { a.Append(fmt.Errorf("empty container")) - if c.n != 0 { - a.Append(fmt.Errorf("empty container with nonzero count: n=%d", c.n)) + if c.N() != 0 { + a.Append(fmt.Errorf("empty container with nonzero count: n=%d", c.N())) } } @@ -2311,6 +2528,11 @@ func (c *Container) check() error { // Repair repairs the cardinality of c if it has been corrupted by // optimized operations. func (c *Container) Repair() { + // a frozen container can't have had n or contents changed, so we + // don't need to recount it. + if c.frozen() { + return + } if c.isBitmap() { c.bitmapRepair() } @@ -2328,7 +2550,7 @@ func (c *Container) bitmapRepair() { n += int32(popcount(bitmap[i+2])) n += int32(popcount(bitmap[i+3])) } - c.n = n + c.setN(n) } // containerInfo represents a point-in-time snapshot of container stats. @@ -2356,20 +2578,20 @@ func flipArray(b *Container) *Container { statsHit("flipArray") // TODO: actually implement this x := b.Clone() - x.arrayToBitmap() + x = x.arrayToBitmap() return flipBitmap(x) } func flipBitmap(b *Container) *Container { statsHit("flipBitmap") - other := NewContainerBitmap(0, nil) + other := NewContainerBitmapN(nil, 0) bitmap := b.bitmap() otherBitmap := other.bitmap() for i, word := range bitmap { otherBitmap[i] = ^word } - other.n = other.count() + other.setN(other.count()) return other } @@ -2377,11 +2599,20 @@ func flipRun(b *Container) *Container { statsHit("flipRun") // TODO: actually implement this x := b.Clone() - x.runToBitmap() + x = x.runToBitmap() return flipBitmap(x) } func intersectionCount(a, b *Container) int32 { + if a.N() == maxContainerVal+1 { + return b.N() + } + if b.N() == maxContainerVal+1 { + return a.N() + } + if a.N() == 0 || b.N() == 0 { + return 0 + } if a.isArray() { if b.isArray() { return intersectionCountArrayArray(a, b) @@ -2413,9 +2644,6 @@ func intersectionCountArrayArray(a, b *Container) (n int32) { statsHit("intersectionCount/ArrayArray") ca, cb := a.array(), b.array() na, nb := len(ca), len(cb) - if na == 0 || nb == 0 { - return 0 - } if na > nb { ca, cb = cb, ca na, nb = nb, na // nolint: ineffassign @@ -2515,6 +2743,15 @@ func intersectionCountBitmapBitmap(a, b *Container) (n int32) { } func intersect(a, b *Container) *Container { + if a.N() == maxContainerVal+1 { + return b.Freeze() + } + if b.N() == maxContainerVal+1 { + return a.Freeze() + } + if a.N() == 0 || b.N() == 0 { + return nil + } if a.isArray() { if b.isArray() { return intersectArrayArray(a, b) @@ -2589,6 +2826,7 @@ func intersectRunRun(a, b *Container) *Container { output := NewContainerRun(nil) ra, rb := a.runs(), b.runs() na, nb := len(ra), len(rb) + n := int32(0) for i, j := 0, 0; i < na && j < nb; { va, vb := ra[i], rb[j] if va.last < vb.start { @@ -2599,24 +2837,25 @@ func intersectRunRun(a, b *Container) *Container { j++ } else if va.last > vb.last && va.start >= vb.start { // |--vb-|-|-va--| - output.n += output.runAppendInterval(interval16{start: va.start, last: vb.last}) + n += output.runAppendInterval(interval16{start: va.start, last: vb.last}) j++ } else if va.last > vb.last && va.start < vb.start { // |--va|--vb--|--| - output.n += output.runAppendInterval(vb) + n += output.runAppendInterval(vb) j++ } else if va.last <= vb.last && va.start >= vb.start { // |--vb|--va--|--| - output.n += output.runAppendInterval(va) + n += output.runAppendInterval(va) i++ } else if va.last <= vb.last && va.start < vb.start { // |--va-|-|-vb--| - output.n += output.runAppendInterval(interval16{start: vb.start, last: va.last}) + n += output.runAppendInterval(interval16{start: vb.start, last: va.last}) i++ } } + output.setN(n) runs := output.runs() - if output.n < ArrayMaxSize && int32(len(runs)) > output.n/2 { + if n < ArrayMaxSize && int32(len(runs)) > n/2 { output.runToArray() } else if len(runs) > runMaxSize { output.runToBitmap() @@ -2630,9 +2869,9 @@ func intersectBitmapRun(a, b *Container) *Container { statsHit("intersect/BitmapRun") var output *Container runs := b.runs() - if b.n <= ArrayMaxSize || a.n <= ArrayMaxSize { + if b.N() <= ArrayMaxSize || a.N() <= ArrayMaxSize { // output is array container - array := make([]uint16, 0, b.n) + array := make([]uint16, 0, b.N()) for _, iv := range runs { for i := iv.start; i <= iv.last; i++ { if a.bitmapContains(i) { @@ -2653,6 +2892,7 @@ func intersectBitmapRun(a, b *Container) *Container { output = NewContainerBitmap(0, nil) bitmap := output.bitmap() aBitmap := a.bitmap() + n := int32(0) for j := 0; j < len(runs); j++ { vb := runs[j] i := vb.start >> 6 // index into a @@ -2661,22 +2901,22 @@ func intersectBitmapRun(a, b *Container) *Container { for valast >= vb.start && vastart <= vb.last && i < bitmapN { if vastart >= vb.start && valast <= vb.last { // a within b bitmap[i] = aBitmap[i] - output.n += int32(popcount(aBitmap[i])) + n += int32(popcount(aBitmap[i])) } else if vb.start >= vastart && vb.last <= valast { // b within a var mask uint64 = ((1 << (vb.last - vb.start + 1)) - 1) << (vb.start - vastart) bits := aBitmap[i] & mask bitmap[i] |= bits - output.n += int32(popcount(bits)) + n += int32(popcount(bits)) } else if vastart < vb.start { // a overlaps front of b offset := 64 - (1 + valast - vb.start) bits := (aBitmap[i] >> offset) << offset bitmap[i] |= bits - output.n += int32(popcount(bits)) + n += int32(popcount(bits)) } else if vb.start < vastart { // b overlaps front of a offset := 64 - (1 + vb.last - vastart) bits := (aBitmap[i] << offset) >> offset bitmap[i] |= bits - output.n += int32(popcount(bits)) + n += int32(popcount(bits)) } // update loop vars i++ @@ -2684,6 +2924,7 @@ func intersectBitmapRun(a, b *Container) *Container { valast = vastart + 63 } } + output.setN(n) } return output } @@ -2719,11 +2960,14 @@ func intersectBitmapBitmap(a, b *Container) *Container { n += int32(popcount(ob[i])) } - output := NewContainerBitmap(n, ob) + output := NewContainerBitmapN(ob, n) return output } func union(a, b *Container) *Container { + if a.N() == maxContainerVal+1 || b.N() == maxContainerVal+1 { + return fullContainer + } if a.isArray() { if b.isArray() { return unionArrayArray(a, b) @@ -2793,7 +3037,7 @@ func unionArrayArray(a, b *Container) *Container { // unionArrayArrayInPlace does what it sounds like -- tries to combine // the two arrays in-place. It does not try to ensure that the result is // of a good array size, so it could be up to twice that size, temporarily. -func unionArrayArrayInPlace(a, b *Container) { +func unionArrayArrayInPlace(a, b *Container) *Container { statsHit("union/ArrayArrayInPlace") aa, ab := a.array(), b.array() na, nb := len(aa), len(ab) @@ -2828,25 +3072,26 @@ func unionArrayArrayInPlace(a, b *Container) { j++ } } - a.setArray(output[:outN]) - a.n = int32(outN) - if a.n > ArrayMaxSize { - a.optimize() + // a union can't omit anything that was previously in a, so if + // the output is the same length, nothing changed. + if len(output) != int(a.N()) { + a = a.Thaw() + a.setArray(output[:outN]) + a = a.optimize() } + return a } // unionArrayRun optimistically assumes that the result will be a run container, // and converts to a bitmap or array container afterwards if necessary. func unionArrayRun(a, b *Container) *Container { statsHit("union/ArrayRun") - if b.n == maxContainerVal+1 { - return b.Clone() - } output := NewContainerRun(nil) aa, rb := a.array(), b.runs() na, nb := len(aa), len(rb) var vb interval16 var va uint16 + n := int32(0) for i, j := 0, 0; i < na || j < nb; { if i < na { va = aa[i] @@ -2855,17 +3100,18 @@ func unionArrayRun(a, b *Container) *Container { vb = rb[j] } if i < na && (j >= nb || va < vb.start) { - output.n += output.runAppendInterval(interval16{start: va, last: va}) + n += output.runAppendInterval(interval16{start: va, last: va}) i++ } else { - output.n += output.runAppendInterval(vb) + n += output.runAppendInterval(vb) j++ } } - if output.n < ArrayMaxSize { - output.runToArray() + output.setN(n) + if n < ArrayMaxSize { + output = output.runToArray() } else if len(output.runs()) > runMaxSize { - output.runToBitmap() + output = output.runToBitmap() } return output } @@ -2902,16 +3148,11 @@ func (c *Container) runAppendInterval(v interval16) int32 { func unionRunRun(a, b *Container) *Container { statsHit("union/RunRun") - if a.n == maxContainerVal+1 { - return a.Clone() - } - if b.n == maxContainerVal+1 { - return b.Clone() - } ra, rb := a.runs(), b.runs() na, nb := len(ra), len(rb) output := NewContainerRun(make([]interval16, 0, na+nb)) var va, vb interval16 + n := int32(0) for i, j := 0, 0; i < na || j < nb; { if i < na { va = ra[i] @@ -2920,13 +3161,14 @@ func unionRunRun(a, b *Container) *Container { vb = rb[j] } if i < na && (j >= nb || va.start < vb.start) { - output.n += output.runAppendInterval(va) + n += output.runAppendInterval(va) i++ } else { - output.n += output.runAppendInterval(vb) + n += output.runAppendInterval(vb) j++ } } + output.setN(n) if len(output.runs()) > runMaxSize { output.runToBitmap() } @@ -2935,55 +3177,52 @@ func unionRunRun(a, b *Container) *Container { func unionBitmapRun(a, b *Container) *Container { statsHit("union/BitmapRun") - if b.n == maxContainerVal+1 { - return b.Clone() - } - if a.n == maxContainerVal+1 { - return a.Clone() - } output := a.Clone() - bitmap := output.bitmap() for _, run := range b.runs() { - output.bitmapSetRange(bitmap, uint64(run.start), uint64(run.last)+1) + output.bitmapSetRange(uint64(run.start), uint64(run.last)+1) } return output } // unions the run b into the bitmap a, mutating a in place. The n value of // a will need to be repaired after the fact. -func unionBitmapRunInPlace(a, b *Container) { - a.unmapBitmap() +func unionBitmapRunInPlace(a, b *Container) *Container { + a = a.Thaw() bitmap := a.bitmap() statsHit("union/BitmapRun") for _, run := range b.runs() { bitmapSetRangeIgnoreN(bitmap, uint64(run.start), uint64(run.last)+1) } + return a } const maxBitmap = 0xFFFFFFFFFFFFFFFF // sets all bits in [i, j) (c must be a bitmap container, and bitmap must // be its bitmap). -func (c *Container) bitmapSetRange(bitmap []uint64, i, j uint64) { +func (c *Container) bitmapSetRange(i, j uint64) { + bitmap := c.bitmap() x := i >> 6 y := (j - 1) >> 6 var X uint64 = maxBitmap << (i % 64) var Y uint64 = maxBitmap >> (63 - ((j - 1) % 64)) xcnt := popcount(X) ycnt := popcount(Y) + n := int32(c.N()) if x == y { - c.n += int32((j - i) - popcount(bitmap[x]&(X&Y))) + n += int32((j - i) - popcount(bitmap[x]&(X&Y))) bitmap[x] |= (X & Y) } else { - c.n += int32(xcnt - popcount(bitmap[x]&X)) + n += int32(xcnt - popcount(bitmap[x]&X)) bitmap[x] |= X for i := x + 1; i < y; i++ { - c.n += int32(64 - popcount(bitmap[i])) + n += int32(64 - popcount(bitmap[i])) bitmap[i] = maxBitmap } - c.n += int32(ycnt - popcount(bitmap[y]&Y)) + n += int32(ycnt - popcount(bitmap[y]&Y)) bitmap[y] |= Y } + c.setN(n) } // sets all bits in [i, j) without updating any corresponding n value. @@ -3011,23 +3250,25 @@ func (c *Container) bitmapXorRange(i, j uint64) { var X uint64 = maxBitmap << (i % 64) var Y uint64 = maxBitmap >> (63 - ((j - 1) % 64)) bitmap := c.bitmap() + n := c.N() if x == y { cnt := popcount(bitmap[x]) bitmap[x] ^= (X & Y) //// flip - c.n += int32(popcount(bitmap[x]) - cnt) + n += int32(popcount(bitmap[x]) - cnt) } else { cnt := popcount(bitmap[x]) bitmap[x] ^= X - c.n += int32(popcount(bitmap[x]) - cnt) + n += int32(popcount(bitmap[x]) - cnt) for i := x + 1; i < y; i++ { cnt = popcount(bitmap[i]) bitmap[i] ^= maxBitmap - c.n += int32(popcount(bitmap[i]) - cnt) + n += int32(popcount(bitmap[i]) - cnt) } cnt = popcount(bitmap[y]) bitmap[y] ^= Y - c.n += int32(popcount(bitmap[y]) - cnt) + n += int32(popcount(bitmap[y]) - cnt) } + c.setN(n) } // zeroes all bits in [i, j) (c must be a bitmap container) @@ -3037,26 +3278,34 @@ func (c *Container) bitmapZeroRange(i, j uint64) { var X uint64 = maxBitmap << (i % 64) var Y uint64 = maxBitmap >> (63 - ((j - 1) % 64)) bitmap := c.bitmap() + n := c.N() if x == y { - c.n -= int32(popcount(bitmap[x] & (X & Y))) + n -= int32(popcount(bitmap[x] & (X & Y))) bitmap[x] &= ^(X & Y) } else { - c.n -= int32(popcount(bitmap[x] & X)) + n -= int32(popcount(bitmap[x] & X)) bitmap[x] &= ^X for i := x + 1; i < y; i++ { - c.n -= int32(popcount(bitmap[i])) + n -= int32(popcount(bitmap[i])) bitmap[i] = 0 } - c.n -= int32(popcount(bitmap[y] & Y)) + n -= int32(popcount(bitmap[y] & Y)) bitmap[y] &= ^Y } + c.setN(n) } +// equals reports whether two containers are equal. func (c *Container) equals(c2 *Container) bool { - if c.mapped != c2.mapped || c.typ != c2.typ || c.n != c2.n { + if c == nil || c2 == nil { + if c != c2 { + return false + } + } + if c.Mapped() != c2.Mapped() || c.typ() != c2.typ() || c.N() != c2.N() { return false } - if c.typ == containerArray { + if c.typ() == containerArray { ca, c2a := c.array(), c2.array() if len(ca) != len(c2a) { return false @@ -3066,7 +3315,7 @@ func (c *Container) equals(c2 *Container) bool { return false } } - } else if c.typ == containerBitmap { + } else if c.typ() == containerBitmap { cb, c2b := c.bitmap(), c2.bitmap() if len(cb) != len(c2b) { return false @@ -3076,7 +3325,7 @@ func (c *Container) equals(c2 *Container) bool { return false } } - } else if c.typ == containerRun { + } else if c.typ() == containerRun { cr, c2r := c.runs(), c2.runs() if len(cr) != len(c2r) { return false @@ -3087,7 +3336,7 @@ func (c *Container) equals(c2 *Container) bool { } } } else { - panic(fmt.Sprintf("unknown container type: %v", c.typ)) + panic(fmt.Sprintf("unknown container type: %v", c.typ())) } return true } @@ -3095,23 +3344,26 @@ func (c *Container) equals(c2 *Container) bool { func unionArrayBitmap(a, b *Container) *Container { output := b.Clone() bitmap := output.bitmap() + n := output.N() for _, v := range a.array() { if !output.bitmapContains(v) { bitmap[v/64] |= (1 << uint64(v%64)) - output.n++ + n++ } } + output.setN(n) return output } // unions array b into bitmap a, mutating a in place. The n value // of a will need to be repaired after the fact. -func unionBitmapArrayInPlace(a, b *Container) { - a.unmapBitmap() +func unionBitmapArrayInPlace(a, b *Container) *Container { + a = a.Thaw() bitmap := a.bitmap() for _, v := range b.array() { bitmap[v>>6] |= (uint64(1) << (v % 64)) } + return a } func unionBitmapBitmap(a, b *Container) *Container { @@ -3131,15 +3383,14 @@ func unionBitmapBitmap(a, b *Container) *Container { n += int32(popcount(ob[i])) } - output := NewContainerBitmap(n, ob) + output := NewContainerBitmapN(ob, n) return output } // unions bitmap b into bitmap a, mutating a in place. The n value of // a will need to be repaired after the fact. -func unionBitmapBitmapInPlace(a, b *Container) { - - a.unmapBitmap() +func unionBitmapBitmapInPlace(a, b *Container) *Container { + a = a.Thaw() // local variables added to prevent BCE checks in loop // see https://go101.org/article/bounds-check-elimination.html @@ -3156,9 +3407,16 @@ func unionBitmapBitmapInPlace(a, b *Container) { ab[i+2] |= bb[i+2] ab[i+3] |= bb[i+3] } + return a } func difference(a, b *Container) *Container { + if a.N() == 0 || b.N() == maxContainerVal+1 { + return nil + } + if b.N() == 0 { + return a.Freeze() + } if a.isArray() { if b.isArray() { return differenceArrayArray(a, b) @@ -3217,12 +3475,7 @@ func differenceArrayArray(a, b *Container) *Container { func differenceArrayRun(a, b *Container) *Container { statsHit("difference/ArrayRun") // func (ac *arrayContainer) iandNotRun16(rc *runContainer16) container { - - if a.n == 0 || b.n == 0 { - return a.Clone() - } - - output := NewContainerArray(make([]uint16, 0, a.n)) + output := make([]uint16, 0, a.N()) // cardinality upper bound: card(A) i := 0 // array index @@ -3230,11 +3483,11 @@ func differenceArrayRun(a, b *Container) *Container { aa, rb := a.array(), b.runs() // handle overlap - for i < int(a.n) { + for i < len(aa) { // keep all array elements before beginning of runs if aa[i] < rb[j].start { - output.add(aa[i]) + output = append(output, aa[i]) i++ continue } @@ -3258,29 +3511,14 @@ func differenceArrayRun(a, b *Container) *Container { // keep all array elements after end of runs // It's possible that output was converted from array to bitmap in output.add() // so check container type before proceeding. - if output.typ == containerArray { - array := output.array() - array = append(array, aa[i:]...) - output.setArray(array) - // TODO: consider handling container.n mutations in one place - // like we do with container.add(). - output.n += int32(len(aa[i:])) - } else { - for _, v := range aa[i:] { - output.add(v) - } - } + output = append(output, aa[i:]...) } - return output + return NewContainerArray(output) } // differenceBitmapRun computes the difference of an bitmap from a run. func differenceBitmapRun(a, b *Container) *Container { statsHit("difference/BitmapRun") - if a.n == 0 || b.n == 0 { - return a.Clone() - } - output := a.Clone() for _, run := range b.runs() { output.bitmapZeroRange(uint64(run.start), uint64(run.last)+1) @@ -3292,9 +3530,6 @@ func differenceBitmapRun(a, b *Container) *Container { // container. func differenceRunArray(a, b *Container) *Container { statsHit("difference/RunArray") - if a.n == 0 || b.n == 0 { - return a.Clone() - } ra, ab := a.runs(), b.array() runs := make([]interval16, 0, len(ra)) @@ -3353,18 +3588,12 @@ func differenceRunBitmap(a, b *Container) *Container { if len(ra) > 0 && ra[0].start == 0 && ra[0].last == 65535 { return flipBitmap(b) } - output := NewContainerRun(nil) - runs := output.runs() - if len(ra) == 0 { - return NewContainerRun(nil) - } - output.n = a.n + runs := make([]interval16, 0, len(ra)) for _, inputRun := range ra { run := inputRun add := true for bit := inputRun.start; bit <= inputRun.last; bit++ { if b.bitmapContains(bit) { - output.n-- if run.start == bit { if bit == 65535 { //overflow add = false @@ -3397,11 +3626,11 @@ func differenceRunBitmap(a, b *Container) *Container { } } - output.setRuns(runs) - if output.n < ArrayMaxSize && int32(len(runs)) > output.n/2 { - output.runToArray() + output := NewContainerRun(runs) + if output.N() < ArrayMaxSize && int32(len(runs)) > output.N()/2 { + output = output.runToArray() } else if len(runs) > runMaxSize { - output.runToBitmap() + output = output.runToBitmap() } return output } @@ -3409,9 +3638,6 @@ func differenceRunBitmap(a, b *Container) *Container { // differenceRunRun computes the difference of two runs. func differenceRunRun(a, b *Container) *Container { statsHit("difference/RunRun") - if a.n == 0 || b.n == 0 { - return a.Clone() - } ra, rb := a.runs(), b.runs() apos := 0 // current a-run index @@ -3472,7 +3698,7 @@ func differenceRunRun(a, b *Container) *Container { func differenceArrayBitmap(a, b *Container) *Container { statsHit("difference/ArrayBitmap") - output := make([]uint16, 0, a.n) + output := make([]uint16, 0, a.N()) bitmap := b.bitmap() for _, va := range a.array() { bmidx := va / 64 @@ -3492,14 +3718,16 @@ func differenceBitmapArray(a, b *Container) *Container { output := a.Clone() bitmap := output.bitmap() + n := output.N() for _, v := range b.array() { if output.bitmapContains(v) { bitmap[v/64] &^= (uint64(1) << uint(v%64)) - output.n-- + n-- } } - if output.n < ArrayMaxSize { - output.bitmapToArray() + output.setN(n) + if n < ArrayMaxSize { + output = output.bitmapToArray() } return output } @@ -3522,14 +3750,20 @@ func differenceBitmapBitmap(a, b *Container) *Container { n += int32(popcount(ob[i])) } - output := NewContainerBitmap(n, ob) - if output.n < ArrayMaxSize { - output.bitmapToArray() + output := NewContainerBitmapN(ob, n) + if output.N() < ArrayMaxSize { + output = output.bitmapToArray() } return output } func xor(a, b *Container) *Container { + if a.N() == 0 { + return b.Freeze() + } + if b.N() == 0 { + return a.Freeze() + } if a.isArray() { if b.isArray() { return xorArrayArray(a, b) @@ -3559,33 +3793,33 @@ func xor(a, b *Container) *Container { func xorArrayArray(a, b *Container) *Container { statsHit("xor/ArrayArray") - output := NewContainerArray(nil) + output := make([]uint16, 0) aa, ab := a.array(), b.array() na, nb := len(aa), len(ab) for i, j := 0, 0; i < na || j < nb; { if i < na && j >= nb { - output.add(aa[i]) + output = append(output, aa[i]) i++ continue } else if i >= na && j < nb { - output.add(ab[j]) + output = append(output, ab[j]) j++ continue } va, vb := aa[i], ab[j] if va < vb { - output.add(va) + output = append(output, va) i++ } else if va > vb { - output.add(vb) + output = append(output, vb) j++ } else { //== i++ j++ } } - return output + return NewContainerArray(output) } func xorArrayBitmap(a, b *Container) *Container { @@ -3593,16 +3827,16 @@ func xorArrayBitmap(a, b *Container) *Container { output := b.Clone() for _, v := range a.array() { if b.bitmapContains(v) { - output.remove(v) + output, _ = output.remove(v) } else { - output.add(v) + output, _ = output.add(v) } } // It's possible that output was converted from bitmap to array in output.remove() // so we only do this conversion if output is still a bitmap container. - if output.typ == containerBitmap && output.count() < ArrayMaxSize { - output.bitmapToArray() + if output.typ() == containerBitmap && output.count() < ArrayMaxSize { + output = output.bitmapToArray() } return output @@ -3626,9 +3860,9 @@ func xorBitmapBitmap(a, b *Container) *Container { n += int32(popcount(ob[i])) } - output := NewContainerBitmap(n, ob) - if output.count() < ArrayMaxSize { - output.bitmapToArray() + output := NewContainerBitmapN(ob, n) + if n < ArrayMaxSize { + output = output.bitmapToArray() } return output } @@ -3637,6 +3871,9 @@ func xorBitmapBitmap(a, b *Container) *Container { // the new container and a bool indicating whether a // carry bit was shifted out. func shift(c *Container) (*Container, bool) { + if c.N() == 0 { + return nil, false + } if c.isArray() { return shiftArray(c) } else if c.isRun() { @@ -3664,23 +3901,18 @@ func shiftArray(a *Container) (*Container, bool) { // shiftBitmap is a bitmap-specific implementation of shift(). func shiftBitmap(a *Container) (*Container, bool) { statsHit("shift/Bitmap") - carry := false - output := NewContainerBitmap(a.n, nil) + carry := uint64(0) + output := NewContainerBitmapN(nil, 0) ba, bo := a.bitmap(), output.bitmap() - lastCarry := false + lastCarry := uint64(0) for i, v := range ba { - carry = (v & (1 << 63)) != 0 - v = v << 1 - if lastCarry { - v |= 1 - } + carry = v >> 63 + v = v<<1 | lastCarry bo[i] = v lastCarry = carry } - if carry { - output.n-- - } - return output, carry + output.setN(a.N() - int32(carry)) + return output, carry != 0 } // shiftRun is a run-specific implementation of shift(). @@ -3962,6 +4194,7 @@ func xorArrayRun(a, b *Container) *Container { var vb interval16 var va uint16 lastI, lastJ := -1, -1 + n := int32((0)) for i, j := 0, 0; i < na || j < nb; { if i < na && i != lastI { va = aa[i] @@ -3973,14 +4206,14 @@ func xorArrayRun(a, b *Container) *Container { lastJ = j if i < na && (j >= nb || va < vb.start) { //before - output.n += output.runAppendInterval(interval16{start: va, last: va}) + n += output.runAppendInterval(interval16{start: va, last: va}) i++ } else if j < nb && (i >= na || va > vb.last) { //after - output.n += output.runAppendInterval(vb) + n += output.runAppendInterval(vb) j++ } else if va > vb.start { if va < vb.last { - output.n += output.runAppendInterval(interval16{start: vb.start, last: va - 1}) + n += output.runAppendInterval(interval16{start: vb.start, last: va - 1}) i++ vb.start = va + 1 @@ -3988,12 +4221,12 @@ func xorArrayRun(a, b *Container) *Container { j++ } } else if va > vb.last { - output.n += output.runAppendInterval(vb) + n += output.runAppendInterval(vb) j++ } else { // va == vb.last vb.last-- if vb.start <= vb.last { - output.n += output.runAppendInterval(vb) + n += output.runAppendInterval(vb) } j++ i++ @@ -4011,10 +4244,11 @@ func xorArrayRun(a, b *Container) *Container { i++ } } - if output.n < ArrayMaxSize { - output.runToArray() + output.setN(n) + if n < ArrayMaxSize { + output = output.runToArray() } else if len(output.runs()) > runMaxSize { - output.runToBitmap() + output = output.runToBitmap() } return output } @@ -4118,18 +4352,13 @@ func xorRunRun(a, b *Container) *Container { statsHit("xor/RunRun") ra, rb := a.runs(), b.runs() na, nb := len(ra), len(rb) - if na == 0 { - return b.Clone() - } - if nb == 0 { - return a.Clone() - } output := NewContainerRun(nil) lastI, lastJ := -1, -1 state := &xorstm{} + n := int32(0) for i, j := 0, 0; i < na || j < nb; { if i < na && lastI != i { state.va = ra[i] @@ -4144,7 +4373,7 @@ func xorRunRun(a, b *Container) *Container { r1, ok := xorCompare(state) if ok { - output.n += output.runAppendInterval(r1) + n += output.runAppendInterval(r1) } if !state.vaValid { i++ @@ -4156,10 +4385,11 @@ func xorRunRun(a, b *Container) *Container { } l := len(output.runs()) - if output.n < ArrayMaxSize && int32(l) > output.n/2 { - output.runToArray() + output.setN(n) + if n < ArrayMaxSize && int32(l) > n/2 { + output = output.runToArray() } else if l > runMaxSize { - output.runToBitmap() + output = output.runToBitmap() } return output } @@ -4346,13 +4576,13 @@ func readOffsets(b *Bitmap, data []byte, pos int, keyN uint32) error { // Map byte slice directly to the container data. citer.Next() _, c := citer.Value() - switch c.typ { + switch c.typ() { case containerArray: - c.setArray((*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[offset]))[:c.n:c.n]) + c.setArray((*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[offset]))[:c.N():c.N()]) case containerBitmap: c.setBitmap((*[0xFFFFFFF]uint64)(unsafe.Pointer(&data[offset]))[:bitmapN:bitmapN]) default: - return fmt.Errorf("unsupported container type %d", c.typ) + return fmt.Errorf("unsupported container type %d", c.typ()) } } return nil @@ -4363,7 +4593,7 @@ func readWithRuns(b *Bitmap, data []byte, pos int, keyN uint32) { for i := 0; i < int(keyN); i++ { citer.Next() _, c := citer.Value() - switch c.typ { + switch c.typ() { case containerRun: runCount := binary.LittleEndian.Uint16(data[pos : pos+runCountHeaderSize]) c.setRuns((*[0xFFFFFFF]interval16)(unsafe.Pointer(&data[pos+runCountHeaderSize]))[:runCount:runCount]) @@ -4374,8 +4604,8 @@ func readWithRuns(b *Bitmap, data []byte, pos int, keyN uint32) { } pos += int((runCount * interval16Size) + runCountHeaderSize) case containerArray: - c.setArray((*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[pos]))[:c.n:c.n]) - pos += int(c.n * 2) + c.setArray((*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[pos]))[:c.N():c.N()]) + pos += int(c.N() * 2) case containerBitmap: c.setBitmap((*[0xFFFFFFF]uint64)(unsafe.Pointer(&data[pos]))[:bitmapN:bitmapN]) pos += bitmapN * 8 @@ -4430,9 +4660,9 @@ func (w handledIters) calculateSummaryStats(key uint64) containerUnionSummarySta if key == currKey { summary.c++ - summary.n += int64(currContainer.n) + summary.n += int64(currContainer.N()) - if currContainer.n == maxContainerVal+1 { + if currContainer.N() == maxContainerVal+1 { summary.hasMaxRange = true summary.n = maxContainerVal + 1 return summary diff --git a/roaring/roaring_helpers_test.go b/roaring/roaring_helpers_test.go index 8c8bbd9ac..41d4059fb 100644 --- a/roaring/roaring_helpers_test.go +++ b/roaring/roaring_helpers_test.go @@ -253,8 +253,7 @@ func doContainer(typ byte, data interface{}) *Container { case containerArray: return NewContainerArray(data.([]uint16)) case containerBitmap: - c := NewContainerBitmap(0, data.([]uint64)) - c.n = c.count() + c := NewContainerBitmap(-1, data.([]uint64)) return c case containerRun: return NewContainerRun(data.([]interval16)) diff --git a/roaring/roaring_internal_test.go b/roaring/roaring_internal_test.go index 8c3e7a930..95c8d1b0b 100644 --- a/roaring/roaring_internal_test.go +++ b/roaring/roaring_internal_test.go @@ -32,10 +32,6 @@ func (iv interval16) String() string { return fmt.Sprintf("[%d, %d]", iv.start, iv.last) } -func (c *Container) String() string { - return fmt.Sprintf("<%s container n=%d, array[%d], runs[%d], bitmap[%d]> type:%s", c.info().Type, c.n, len(c.array()), len(c.runs()), len(c.bitmap()), containerTypeNames[c.typ]) -} - func TestRunAppendInterval(t *testing.T) { a := NewContainerRun(nil) tests := []struct { @@ -102,15 +98,15 @@ func TestContainerRunAdd(t *testing.T) { {8, []interval16{{start: 0, last: 4}, {start: 6, last: 8}, {start: 10, last: 10}}}, } for _, test := range tests { - c.mapped = true - ret := c.add(test.op) - if !ret { + c.setMapped(true) + c, changed := c.add(test.op) + if !changed { t.Fatalf("result of adding new bit should be true: %v", c.runs()) } if !reflect.DeepEqual(c.runs(), test.exp) { t.Fatalf("Should have %v, but got %v after adding %v", test.exp, c.runs(), test.op) } - if c.mapped { + if c.Mapped() { t.Fatalf("container should not be mapped after adding bit %v", test.op) } } @@ -118,14 +114,14 @@ func TestContainerRunAdd(t *testing.T) { func TestContainerRunAdd2(t *testing.T) { c := NewContainerRun(nil) - ret := c.add(0) + c, ret := c.add(0) if !ret { t.Fatalf("result of adding new bit should be true: %v", c.runs()) } if !reflect.DeepEqual(c.runs(), []interval16{{start: 0, last: 0}}) { t.Fatalf("should have 1 run of length 1, but have %v", c.runs()) } - ret = c.add(0) + c, ret = c.add(0) if ret { t.Fatalf("result of adding existing bit should be false: %v", c.runs()) } @@ -237,23 +233,23 @@ func TestBitmapCountRange(t *testing.T) { } func TestIntersectionCountArrayBitmap3(t *testing.T) { - a, b := NewContainerBitmap(maxContainerVal+1, getFullBitmap()), NewContainerBitmap(maxContainerVal+1, getFullBitmap()) + a, b := NewContainerBitmapN(getFullBitmap(), maxContainerVal+1), NewContainerBitmapN(getFullBitmap(), maxContainerVal+1) res := intersectBitmapBitmap(a, b) - if res.n != res.count() || res.n != maxContainerVal+1 { - t.Fatalf("test #1 intersectCountBitmapBitmap fail orig: %v new: %v exp: %v", res.n, res.count(), maxContainerVal+1) + if res.N() != res.count() || res.N() != maxContainerVal+1 { + t.Fatalf("test #1 intersectCountBitmapBitmap fail orig: %v new: %v exp: %v", res.N(), res.count(), maxContainerVal+1) } - a.bitmapToRun(0) + a = a.bitmapToRun(0) res = intersectBitmapRun(b, a) - if res.n != res.count() || res.n != maxContainerVal+1 { - t.Fatalf("test #2 intersectCountBitmapRun fail orig: %v new: %v exp: %v", res.n, res.count(), maxContainerVal+1) + if res.N() != res.count() || res.N() != maxContainerVal+1 { + t.Fatalf("test #2 intersectCountBitmapRun fail orig: %v new: %v exp: %v", res.N(), res.count(), maxContainerVal+1) } b.bitmapToRun(0) res = intersectRunRun(a, b) n := intersectionCountRunRun(a, b) - if res.n != res.count() || res.n != maxContainerVal+1 || res.n != int32(n) { - t.Fatalf("test #3 intersectCountRunRun fail orig: %v new: %v exp: %v", res.n, res.count(), maxContainerVal+1) + if res.N() != res.count() || res.N() != maxContainerVal+1 || res.N() != int32(n) { + t.Fatalf("test #3 intersectCountRunRun fail orig: %v new: %v exp: %v", res.N(), res.count(), maxContainerVal+1) } } @@ -321,15 +317,16 @@ func TestRunRemove(t *testing.T) { } for i, test := range tests { - c.mapped = true - ret := c.remove(test.op) + c = c.Freeze() + var ret bool + c, ret = c.remove(test.op) if ret != test.expRet || !reflect.DeepEqual(c.runs(), test.exp) { t.Fatalf("test #%v Unexpected result removing %v from runs. Expected %v, got %v. Expected %v, got %v", i, test.op, test.expRet, ret, test.exp, c.runs()) } - if ret && c.mapped { + if ret && c.frozen() { t.Fatalf("test #%v container was not unmapped although bit %v was removed", i, test.op) } - if !ret && !c.mapped { + if !ret && !c.frozen() { t.Fatalf("test #%v container was unmapped although bit %v was not removed", i, test.op) } } @@ -370,7 +367,7 @@ func TestIntersectionCountBitmapRun(t *testing.T) { t.Fatalf("count of %v with %v should be 1, but got %v", a.bitmap(), b.runs(), ret) } - a = NewContainerBitmap(29, []uint64{0xF0000001, 0xFF00000000000000, 0xFF000000000000F0, 0x0F0000}) + a = NewContainerBitmap(-1, []uint64{0xF0000001, 0xFF00000000000000, 0xFF000000000000F0, 0x0F0000}) b = NewContainerRun([]interval16{{start: 29, last: 31}, {start: 125, last: 134}, {start: 191, last: 197}, {start: 200, last: 300}}) ret = intersectionCountBitmapRun(a, b) @@ -380,8 +377,6 @@ func TestIntersectionCountBitmapRun(t *testing.T) { } func TestIntersectionCountRunRun(t *testing.T) { - a := NewContainerRun(nil) - b := NewContainerRun(nil) tests := []struct { aruns []interval16 bruns []interval16 @@ -419,10 +414,8 @@ func TestIntersectionCountRunRun(t *testing.T) { bruns: []interval16{{start: 9, last: 9}, {start: 11, last: 17}}, exp: 6}, } for i, test := range tests { - a.typ = containerRun - b.typ = containerRun - a.setRuns(test.aruns) - b.setRuns(test.bruns) + a := NewContainerRun(test.aruns) + b := NewContainerRun(test.bruns) ret := intersectionCountRunRun(a, b) if ret != test.exp { t.Fatalf("test #%v failed intersecting %v with %v should be %v, but got %v", i, test.aruns, test.bruns, test.exp, ret) @@ -524,8 +517,8 @@ func TestIntersectRunRun(t *testing.T) { a.setRuns(test.aruns) b.setRuns(test.bruns) ret := intersectRunRun(a, b) - if ret.n != test.expN { - t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.n) + if ret.N() != test.expN { + t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.N()) } if test.exp != nil { if !reflect.DeepEqual(ret.runs(), test.exp) { @@ -538,8 +531,6 @@ func TestIntersectRunRun(t *testing.T) { } func TestIntersectBitmapRunBitmap(t *testing.T) { - a := NewContainerBitmap(0, nil) - b := NewContainerRun(nil) tests := []struct { bitmap []uint64 runs []interval16 @@ -578,13 +569,11 @@ func TestIntersectBitmapRunBitmap(t *testing.T) { }, } for i, test := range tests { - copy(a.bitmap(), test.bitmap) - b.setRuns(test.runs) - b.n = 4097 // ;) exp := make([]uint64, bitmapN) copy(exp, test.exp) - a.typ = containerBitmap - b.typ = containerRun + a := NewContainerBitmap(-1, test.bitmap) + b := NewContainerRun(test.runs) + b.setN(4097) ret := intersectBitmapRun(a, b) if ret.isArray() { ret.arrayToBitmap() @@ -592,8 +581,8 @@ func TestIntersectBitmapRunBitmap(t *testing.T) { if !reflect.DeepEqual(ret.bitmap(), exp) { t.Fatalf("test #%v expected %v, but got %v", i, exp, ret.bitmap()) } - if ret.n != test.expN { - t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.n) + if ret.N() != test.expN { + t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.N()) } } @@ -646,8 +635,8 @@ func TestIntersectBitmapRunArray(t *testing.T) { if !reflect.DeepEqual(ret.array(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, ret.array()) } - if ret.n != test.expN { - t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.n) + if ret.N() != test.expN { + t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.N()) } } @@ -738,8 +727,7 @@ func TestDifferenceMixed(t *testing.T) { b := NewContainerArray([]uint16{0, 2, 4, 6, 8, 10, 12}) - c := NewContainerBitmap(0, MakeBitmap([]uint64{0x64})) - c.n = c.countRange(0, 100) + c := NewContainerBitmap(-1, MakeBitmap([]uint64{0x64})) d := NewContainerArray([]uint16{1, 3, 5, 7, 9, 11, 12}) @@ -780,13 +768,13 @@ func TestDifferenceMixed(t *testing.T) { } res = difference(b, b) - if res.n != 0 { - t.Fatalf("test #8 expected 0, but got %d", res.n) + if res.N() != 0 { + t.Fatalf("test #8 expected 0, but got %d", res.N()) } res = difference(c, c) - if res.n != 0 { - t.Fatalf("test #9 expected 0, but got %d", res.n) + if res.N() != 0 { + t.Fatalf("test #9 expected 0, but got %d", res.N()) } res = difference(d, b) @@ -901,7 +889,6 @@ func TestUnionArrayRun(t *testing.T) { } func TestBitmapSetRange(t *testing.T) { - c := NewContainerBitmap(0, nil) tests := []struct { bitmap []uint64 start uint64 @@ -926,21 +913,18 @@ func TestBitmapSetRange(t *testing.T) { } for i, test := range tests { - bitmap := c.bitmap() - copy(bitmap, test.bitmap) - c.n = c.countRange(0, 65535) - c.bitmapSetRange(bitmap, test.start, test.last+1) + c := NewContainerBitmap(-1, test.bitmap) + c.bitmapSetRange(test.start, test.last+1) if !reflect.DeepEqual(c.bitmap()[:len(test.exp)], test.exp) { t.Fatalf("test %#v expected %x, got %x", i, test.exp, c.bitmap()[:len(test.bitmap)]) } - if test.expN != c.n { - t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, c.n) + if test.expN != c.N() { + t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, c.N()) } } } func TestArrayToBitmap(t *testing.T) { - a := NewContainerArray(nil) tests := []struct { array []uint16 exp []uint64 @@ -958,19 +942,15 @@ func TestArrayToBitmap(t *testing.T) { for i, test := range tests { exp := make([]uint64, bitmapN) copy(exp, test.exp) - - a.setArray(test.array) - a.n = int32(len(test.array)) - a.arrayToBitmap() + a := NewContainerArray(test.array) + a = a.arrayToBitmap() if !reflect.DeepEqual(a.bitmap(), exp) { t.Fatalf("test #%v expected %v, but got %v", i, exp, a.bitmap()) } - a.bitmapToArray() } } func TestBitmapToArray(t *testing.T) { - a := NewContainerBitmap(0, nil) tests := []struct { bitmap []uint64 exp []uint16 @@ -985,25 +965,16 @@ func TestBitmapToArray(t *testing.T) { }, } for i, test := range tests { - a.setBitmap(make([]uint64, bitmapN)) - bitmap := a.bitmap() - n := int32(0) - for i, v := range test.bitmap { - bitmap[i] = v - n += int32(popcount(v)) - } - a.n = n + a := NewContainerBitmap(-1, test.bitmap) - a.bitmapToArray() + a = a.bitmapToArray() if !reflect.DeepEqual(a.array(), test.exp) { t.Fatalf("test #%v expected %#v, but got %#v", i, test.exp, a.array()) } - a.arrayToBitmap() } } func TestRunToBitmap(t *testing.T) { - a := NewContainerRun(nil) tests := []struct { runs []interval16 exp []uint64 @@ -1037,11 +1008,8 @@ func TestRunToBitmap(t *testing.T) { exp[i] = v n += int(popcount(v)) } - - a.typ = containerRun - a.setRuns(test.runs) - a.n = int32(n) - a.runToBitmap() + a := NewContainerRun(test.runs) + a = a.runToBitmap() if !reflect.DeepEqual(a.bitmap(), exp) { t.Fatalf("test #%v expected %v, but got %v", i, exp, a.bitmap()) } @@ -1058,7 +1026,6 @@ func getFullBitmap() []uint64 { } func TestBitmapToRun(t *testing.T) { - a := NewContainerBitmap(0, nil) tests := []struct { bitmap []uint64 exp []interval16 @@ -1116,15 +1083,8 @@ func TestBitmapToRun(t *testing.T) { tests[8].bitmap[1023] = 0xFFFFFFFFFFFFFFFF for i, test := range tests { - a.setBitmap(make([]uint64, bitmapN)) - bitmap := a.bitmap() - n := 0 - for i, v := range test.bitmap { - bitmap[i] = v - n += int(popcount(v)) - } - a.n = int32(n) - x := bitmap + a := NewContainerBitmap(-1, test.bitmap) + x := a.bitmap() a.bitmapToRun(0) if !reflect.DeepEqual(a.runs(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, a.runs()) @@ -1137,7 +1097,6 @@ func TestBitmapToRun(t *testing.T) { } func TestArrayToRun(t *testing.T) { - a := NewContainerArray(nil) tests := []struct { array []uint16 exp []interval16 @@ -1161,10 +1120,8 @@ func TestArrayToRun(t *testing.T) { } for i, test := range tests { - a.typ = containerArray - a.setArray(test.array) - a.n = int32(len(test.array)) - a.arrayToRun(0) + a := NewContainerArray(test.array) + a = a.arrayToRun(0) if !reflect.DeepEqual(a.runs(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, a.runs()) } @@ -1172,7 +1129,6 @@ func TestArrayToRun(t *testing.T) { } func TestRunToArray(t *testing.T) { - a := NewContainerRun(nil) tests := []struct { runs []interval16 exp []uint16 @@ -1196,10 +1152,8 @@ func TestRunToArray(t *testing.T) { } for i, test := range tests { - a.typ = containerRun - a.setRuns(test.runs) - a.n = int32(len(test.exp)) - a.runToArray() + a := NewContainerRun(test.runs) + a = a.runToArray() if !reflect.DeepEqual(a.array(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, a.array()) } @@ -1207,7 +1161,6 @@ func TestRunToArray(t *testing.T) { } func TestBitmapZeroRange(t *testing.T) { - c := NewContainerBitmap(0, nil) tests := []struct { bitmap []uint64 start uint64 @@ -1230,17 +1183,16 @@ func TestBitmapZeroRange(t *testing.T) { expN: 13, }, } - bitmap := c.bitmap() for i, test := range tests { - copy(bitmap, test.bitmap) - c.n = c.countRange(0, 65535) + c := NewContainerBitmap(-1, test.bitmap) + bitmap := c.bitmap() c.bitmapZeroRange(test.start, test.last+1) if !reflect.DeepEqual(bitmap[:len(test.exp)], test.exp) { t.Fatalf("test %#v expected %x, got %x", i, test.exp, bitmap[:len(test.bitmap)]) } - if test.expN != c.n { - t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, c.n) + if test.expN != c.N() { + t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, c.N()) } for i := range test.bitmap { bitmap[i] = 0 @@ -1250,8 +1202,6 @@ func TestBitmapZeroRange(t *testing.T) { } func TestUnionBitmapRun(t *testing.T) { - a := NewContainerBitmap(0, nil) - b := NewContainerRun(nil) tests := []struct { bitmap []uint64 runs []interval16 @@ -1266,20 +1216,18 @@ func TestUnionBitmapRun(t *testing.T) { }, } for i, test := range tests { - copy(a.bitmap(), test.bitmap) - a.n = a.bitmapCountRange(0, 65535) - b.setRuns(test.runs) - b.n = b.runCountRange(0, 65535) + a := NewContainerBitmap(-1, test.bitmap) + b := NewContainerRun(test.runs) ret := unionBitmapRun(a, b) if ret.isArray() { - ret.arrayToBitmap() + ret = ret.arrayToBitmap() } bitmap := ret.bitmap() if !reflect.DeepEqual(bitmap[:len(test.exp)], test.exp) { t.Fatalf("test #%v expected %x, but got %x", i, test.exp, bitmap[:len(test.exp)]) } - if ret.n != test.expN { - t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.n) + if ret.N() != test.expN { + t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, ret.N()) } for i := range test.bitmap { a.bitmap()[i] = 0 @@ -1288,7 +1236,6 @@ func TestUnionBitmapRun(t *testing.T) { } func TestBitmapCountRuns(t *testing.T) { - c := NewContainerBitmap(0, nil) tests := []struct { bitmap []uint64 exp int32 @@ -1310,10 +1257,10 @@ func TestBitmapCountRuns(t *testing.T) { exp: 10, }, } + var c *Container for i, test := range tests { - copy(c.bitmap(), test.bitmap) - + c = NewContainerBitmap(-1, test.bitmap) ret := c.bitmapCountRuns() if ret != test.exp { t.Fatalf("test #%v expected %v but got %v", i, test.exp, ret) @@ -1377,8 +1324,6 @@ func TestArrayCountRuns(t *testing.T) { } func TestDifferenceArrayRun(t *testing.T) { - a := NewContainerArray(nil) - b := NewContainerRun(nil) tests := []struct { array []uint16 runs []interval16 @@ -1391,10 +1336,8 @@ func TestDifferenceArrayRun(t *testing.T) { }, } for i, test := range tests { - a.setArray(test.array) - a.n = int32(len(a.array())) - b.setRuns(test.runs) - b.n = b.runCountRange(0, 100) + a := NewContainerArray(test.array) + b := NewContainerRun(test.runs) ret := differenceArrayRun(a, b) if !reflect.DeepEqual(ret.array(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, ret.array()) @@ -1403,8 +1346,6 @@ func TestDifferenceArrayRun(t *testing.T) { } func TestDifferenceRunArray(t *testing.T) { - a := NewContainerRun(nil) - b := NewContainerArray(nil) tests := []struct { runs []interval16 array []uint16 @@ -1457,10 +1398,8 @@ func TestDifferenceRunArray(t *testing.T) { }, } for i, test := range tests { - a.setRuns(test.runs) - a.n = a.runCountRange(0, 100) - b.setArray(test.array) - b.n = int32(len(b.array())) + a := NewContainerRun(test.runs) + b := NewContainerArray(test.array) ret := differenceRunArray(a, b) if !reflect.DeepEqual(ret.runs(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, ret.runs()) @@ -1480,8 +1419,6 @@ func MakeLastBitSet() []uint64 { } func TestDifferenceRunBitmap(t *testing.T) { - a := NewContainerRun(nil) - b := NewContainerBitmap(0, nil) tests := []struct { runs []interval16 bitmap []uint64 @@ -1529,10 +1466,8 @@ func TestDifferenceRunBitmap(t *testing.T) { }, } for i, test := range tests { - a.setRuns(test.runs) - a.n = a.runCountRange(0, 65536) - copy(b.bitmap(), test.bitmap) - b.n = b.bitmapCountRange(0, 65536) + a := NewContainerRun(test.runs) + b := NewContainerBitmap(-1, test.bitmap) ret := differenceRunBitmap(a, b) if !reflect.DeepEqual(ret.runs(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, ret.runs()) @@ -1541,8 +1476,6 @@ func TestDifferenceRunBitmap(t *testing.T) { } func TestDifferenceBitmapRun(t *testing.T) { - a := NewContainerBitmap(0, nil) - b := NewContainerRun(nil) tests := []struct { bitmap []uint64 runs []interval16 @@ -1610,10 +1543,8 @@ func TestDifferenceBitmapRun(t *testing.T) { }, } for i, test := range tests { - copy(a.bitmap(), test.bitmap) - a.n = a.bitmapCountRange(0, 65536) - b.setRuns(test.runs) - b.n = b.runCountRange(0, 65536) + a := NewContainerBitmap(-1, test.bitmap) + b := NewContainerRun(test.runs) ret := differenceBitmapRun(a, b) if !reflect.DeepEqual(ret.bitmap()[:len(test.exp)], test.exp) { t.Fatalf("test #%v expected \n%X, but got \n%X", i, test.exp, ret.bitmap()[:len(test.exp)]) @@ -1622,8 +1553,6 @@ func TestDifferenceBitmapRun(t *testing.T) { } func TestDifferenceBitmapArray(t *testing.T) { - b := NewContainerBitmap(0, nil) - a := NewContainerArray(nil) tests := []struct { bitmap []uint64 array []uint16 @@ -1661,9 +1590,8 @@ func TestDifferenceBitmapArray(t *testing.T) { }, } for i, test := range tests { - b.bitmap()[0] = test.bitmap[0] - b.n = b.count() - a.setArray(test.array) + b := NewContainerBitmap(-1, test.bitmap[:1]) + a := NewContainerArray(test.array) ret := differenceBitmapArray(b, a) if !reflect.DeepEqual(ret.array(), test.exp) { t.Fatalf("test #%v expected %#v, but got %#v", i, test.exp, ret.array()) @@ -1672,8 +1600,6 @@ func TestDifferenceBitmapArray(t *testing.T) { } func TestDifferenceBitmapBitmap(t *testing.T) { - a := NewContainerBitmap(0, nil) - b := NewContainerBitmap(0, nil) tests := []struct { abitmap []uint64 bbitmap []uint64 @@ -1691,9 +1617,8 @@ func TestDifferenceBitmapBitmap(t *testing.T) { }, } for i, test := range tests { - a.bitmap()[0] = test.abitmap[0] - b.bitmap()[0] = test.bbitmap[0] - + a := NewContainerBitmap(-1, test.abitmap) + b := NewContainerBitmap(-1, test.bbitmap) ret := differenceBitmapBitmap(a, b) if !reflect.DeepEqual(ret.array(), test.exp) { t.Fatalf("test #%v expected \n%X, but got \n%X", i, test.exp, ret.array()) @@ -1702,8 +1627,6 @@ func TestDifferenceBitmapBitmap(t *testing.T) { } func TestDifferenceRunRun(t *testing.T) { - a := NewContainerRun(nil) - b := NewContainerRun(nil) tests := []struct { aruns []interval16 bruns []interval16 @@ -1721,16 +1644,14 @@ func TestDifferenceRunRun(t *testing.T) { }, } for i, test := range tests { - a.setRuns(test.aruns) - a.n = a.runCountRange(0, 100) - b.setRuns(test.bruns) - b.n = b.runCountRange(0, 100) + a := NewContainerRun(test.aruns) + b := NewContainerRun(test.bruns) ret := differenceRunRun(a, b) if !reflect.DeepEqual(ret.runs(), test.exp) { t.Fatalf("test #%v expected %v, but got %v", i, test.exp, ret.runs()) } - if ret.n != test.expn { - t.Fatalf("test #%v expected n=%v, but got n=%v", i, test.expn, ret.n) + if ret.N() != test.expn { + t.Fatalf("test #%v expected n=%v, but got n=%v", i, test.expn, ret.N()) } } } @@ -1756,7 +1677,7 @@ func TestWriteReadArray(t *testing.T) { func TestWriteReadBitmap(t *testing.T) { // create bitmap containing > 4096 bits - cb := NewContainerBitmap(129*32, nil) + cb := NewContainerBitmapN(nil, 129*32) for i := 0; i < 129; i++ { cb.bitmap()[i] = 0x5555555555555555 } @@ -1779,7 +1700,7 @@ func TestWriteReadBitmap(t *testing.T) { func TestWriteReadFullBitmap(t *testing.T) { // create bitmap containing > 4096 bits - cb := NewContainerBitmap(65536, nil) + cb := NewContainerBitmapN(nil, 65536) for i := 0; i < bitmapN; i++ { cb.bitmap()[i] = 0xffffffffffffffff } @@ -1802,11 +1723,11 @@ func TestWriteReadFullBitmap(t *testing.T) { t.Fatalf("bitmap test expected %x, but got %x", cb.bitmap(), bb2.Containers.Get(0).bitmap()) } - if bb2.Containers.Get(0).n != cb.n { - t.Fatalf("bitmap test expected count %x, but got %x", cb.n, bb2.Containers.Get(0).n) + if bb2.Containers.Get(0).N() != cb.N() { + t.Fatalf("bitmap test expected count %x, but got %x", cb.N(), bb2.Containers.Get(0).N()) } if bb2.Containers.Get(0).count() != cb.count() { - t.Fatalf("bitmap test expected count %x, but got %x", cb.n, bb2.Containers.Get(0).n) + t.Fatalf("bitmap test expected count %x, but got %x", cb.N(), bb2.Containers.Get(0).N()) } } @@ -1855,8 +1776,8 @@ func TestXorArrayRun(t *testing.T) { } for i, test := range tests { - test.a.n = test.a.count() - test.b.n = test.b.count() + test.a.setN(test.a.count()) + test.b.setN(test.b.count()) ret := xor(test.a, test.b) if !reflect.DeepEqual(ret.array(), test.exp.array()) { t.Fatalf("test #%v expected %#v, but got %#v", i, test.exp, ret) @@ -1982,7 +1903,6 @@ func TestXorRunRun(t *testing.T) { } func TestBitmapXorRange(t *testing.T) { - c := NewContainerBitmap(0, nil) tests := []struct { bitmap []uint64 start uint64 @@ -2035,14 +1955,13 @@ func TestBitmapXorRange(t *testing.T) { } for i, test := range tests { - copy(c.bitmap(), test.bitmap) - c.n = c.countRange(0, 65535) + c := NewContainerBitmap(-1, test.bitmap) c.bitmapXorRange(test.start, test.last+1) if !reflect.DeepEqual(c.bitmap()[:len(test.exp)], test.exp) { t.Fatalf("test %#v expected %x, got %x", i, test.exp, c.bitmap()[:len(test.bitmap)]) } - if test.expN != c.n { - t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, c.n) + if test.expN != c.N() { + t.Fatalf("test #%v expected n to be %v, but got %v", i, test.expN, c.N()) } } } @@ -2060,15 +1979,21 @@ func TestXorBitmapRun(t *testing.T) { }, } for i, test := range tests { - a := NewContainerBitmap(0, test.bitmap) - e := NewContainerBitmap(0, test.exp) + a := NewContainerBitmap(-1, test.bitmap) + e := NewContainerBitmap(-1, test.exp) b := NewContainerRun(test.runs) //xorBitmapRun ret := xor(a, b) + if ret.isRun() { + ret = ret.runToBitmap() + } if !reflect.DeepEqual(ret.bitmap(), e.bitmap()) { t.Fatalf("test #%v expected %v, but got %v", i, e.bitmap(), ret.bitmap()) } ret = xor(b, a) + if ret.isRun() { + ret = ret.runToBitmap() + } if !reflect.DeepEqual(ret.bitmap(), e.bitmap()) { t.Fatalf("test #%v.1 expected %v, but got %v", i, e.bitmap(), ret.bitmap()) } @@ -2397,15 +2322,14 @@ func TestRunBinSearch(t *testing.T) { } func TestBitmap_RemoveEmptyContainers(t *testing.T) { bm1 := NewFileBitmap(1<<16, 2<<16, 3<<16) - if _, err := bm1.Remove(2 << 16); err != nil { - t.Fatalf("removing a bit: %v", err) - } - if bm1.countEmptyContainers() != 1 { + bm2 := NewFileBitmap(1<<16, 2<<16+1, 3<<16) + bm3 := bm1.Intersect(bm2) + if bm3.countEmptyContainers() != 1 { t.Fatalf("Should be 1 empty container ") } - bm1.removeEmptyContainers() + bm3.removeEmptyContainers() - if bm1.countEmptyContainers() != 0 { + if bm3.countEmptyContainers() != 0 { t.Fatalf("Should be no empty containers ") } } @@ -2510,7 +2434,6 @@ func TestSearch64(t *testing.T) { } func TestIntersectArrayBitmap(t *testing.T) { - a, b := NewContainerArray(nil), NewContainerBitmap(0, nil) tests := []struct { array []uint16 bitmap []uint64 @@ -2554,8 +2477,8 @@ func TestIntersectArrayBitmap(t *testing.T) { } for i, test := range tests { - a.setArray(test.array) - copy(b.bitmap(), test.bitmap) + a := NewContainerArray(test.array) + b := NewContainerBitmap(-1, test.bitmap) ret := intersectArrayBitmap(a, b).array() if len(ret) == 0 && len(test.exp) == 0 { continue @@ -3274,42 +3197,48 @@ func TestContainerCombinations(t *testing.T) { for _, ct := range containerTypes { clone := ret.Clone() if ct == containerArray { - if clone.isBitmap() { - clone.bitmapToArray() + if clone == nil { + clone = NewContainerArray(nil) + } else if clone.isBitmap() { + clone = clone.bitmapToArray() } else if clone.isRun() { - clone.runToArray() + clone = clone.runToArray() } - if clone.n != cts[ct][exp].n { - t.Fatalf("test %s expected array n=%d, but got n=%d", desc, cts[ct][exp].n, clone.n) + if clone.N() != cts[ct][exp].N() { + t.Errorf("test %s expected array n=%d, but got n=%d", desc, cts[ct][exp].N(), clone.N()) } // Because xorRunRun resulting in an empty container returns an array container with a // nil slice array, then we need to check len() on array first (look for 0). if !(len(clone.array()) == 0 && len(cts[ct][exp].array()) == 0) && !reflect.DeepEqual(clone.array(), cts[ct][exp].array()) { - t.Fatalf("test %s expected array %X, but got %X", desc, cts[ct][exp].array(), clone.array()) + t.Errorf("test %s expected array %X, but got %X", desc, cts[ct][exp].array(), clone.array()) } } else if ct == containerBitmap { - if clone.isArray() { - clone.arrayToBitmap() + if clone == nil { + clone = NewContainerBitmap(0, nil) + } else if clone.isArray() { + clone = clone.arrayToBitmap() } else if clone.isRun() { - clone.runToBitmap() + clone = clone.runToBitmap() } - if clone.n != cts[ct][exp].n { - t.Fatalf("test %s expected bitmap n=%d, but got n=%d", desc, cts[ct][exp].n, clone.n) + if clone.N() != cts[ct][exp].N() { + t.Errorf("test %s expected bitmap n=%d, but got n=%d", desc, cts[ct][exp].N(), clone.N()) } if !reflect.DeepEqual(clone.bitmap(), cts[ct][exp].bitmap()) { - t.Fatalf("test %s expected bitmap %X, but got %X", desc, cts[ct][exp].bitmap(), clone.bitmap()) + t.Errorf("test %s expected bitmap %X, but got %X", desc, cts[ct][exp].bitmap(), clone.bitmap()) } } else if ct == containerRun { - if clone.isArray() { - clone.arrayToRun(0) + if clone == nil { + clone = NewContainerRun(nil) + } else if clone.isArray() { + clone = clone.arrayToRun(0) } else if clone.isBitmap() { - clone.bitmapToRun(0) + clone = clone.bitmapToRun(0) } - if clone.n != cts[ct][exp].n { - t.Fatalf("test %s expected runs n=%d, but got n=%d", desc, cts[ct][exp].n, clone.n) + if clone.N() != cts[ct][exp].N() { + t.Errorf("test %s expected runs n=%d, but got n=%d", desc, cts[ct][exp].N(), clone.N()) } if !reflect.DeepEqual(clone.runs(), cts[ct][exp].runs()) { - t.Fatalf("test %s expected runs %X, but got %X", desc, cts[ct][exp].runs(), clone.runs()) + t.Errorf("test %s expected runs %X, but got %X", desc, cts[ct][exp].runs(), clone.runs()) } } } @@ -3405,7 +3334,6 @@ func TestEquals(t *testing.T) { } */ func TestShiftArray(t *testing.T) { - a := NewContainerArray(nil) tests := []struct { array []uint16 exp []uint16 @@ -3429,10 +3357,16 @@ func TestShiftArray(t *testing.T) { } for i, test := range tests { - a.setArray(test.array) - a.n = int32(len(a.array())) + a := NewContainerArray(test.array) ret1, _ := shift(a) // test generic shift function ret2, _ := shiftArray(a) // test array-specific shift function + // accept nil *Container as valid substitute for empty array + if ret1 == nil { + ret1 = NewContainerArray(nil) + } + if ret2 == nil { + ret2 = NewContainerArray(nil) + } if !reflect.DeepEqual(ret1.array(), test.exp) { t.Fatalf("test #%v shift() expected %v, but got %v", i, test.exp, ret1.array()) } else if !reflect.DeepEqual(ret2.array(), test.exp) { @@ -3443,7 +3377,6 @@ func TestShiftArray(t *testing.T) { func TestShiftBitmap(t *testing.T) { // note, bitmaps are provided for us by the ensuing tests - a := NewContainerBitmap(0, nil) tests := []struct { bitmap []uint64 exp []uint64 @@ -3463,11 +3396,10 @@ func TestShiftBitmap(t *testing.T) { } for i, test := range tests { - a.setBitmap(test.bitmap) - a.n = 1 + a := NewContainerBitmap(-1, test.bitmap) ret1, _ := shift(a) // test generic shift function ret2, _ := shiftBitmap(a) // test bitmap-specific shift function - e := NewContainerBitmap(1, test.exp) + e := NewContainerBitmap(-1, test.exp) if !reflect.DeepEqual(ret1.bitmap(), e.bitmap()) { t.Fatalf("test #%v shift() expected %v, but got %v", i, e.bitmap(), ret1.bitmap()) } else if !reflect.DeepEqual(ret2.bitmap(), test.exp) { @@ -3476,8 +3408,6 @@ func TestShiftBitmap(t *testing.T) { } } func TestShiftRun(t *testing.T) { - a := NewContainerRun(nil) - tests := []struct { runs []interval16 n int32 @@ -3509,14 +3439,13 @@ func TestShiftRun(t *testing.T) { } for i, test := range tests { - a.setRuns(test.runs) - a.n = test.n + a := NewContainerRun(test.runs) ret1, c1 := shift(a) // test generic shift function ret2, c2 := shiftRun(a) // test run-specific shift function - if !reflect.DeepEqual(ret1.runs(), test.exp) && c1 == test.carry && ret1.n == test.en { - t.Fatalf("test #%v shift() expected %v, but got %v %d", i, test.exp, ret1.runs(), ret1.n) - } else if !reflect.DeepEqual(ret2.runs(), test.exp) && c2 == test.carry && ret2.n == test.en { - t.Fatalf("test #%v shiftRun() expected %v, but got %v %d", i, test.exp, ret2.runs(), ret2.n) + if !reflect.DeepEqual(ret1.runs(), test.exp) && c1 == test.carry && ret1.N() == test.en { + t.Fatalf("test #%v shift() expected %v, but got %v %d", i, test.exp, ret1.runs(), ret1.N()) + } else if !reflect.DeepEqual(ret2.runs(), test.exp) && c2 == test.carry && ret2.N() == test.en { + t.Fatalf("test #%v shiftRun() expected %v, but got %v %d", i, test.exp, ret2.runs(), ret2.N()) } } } diff --git a/roaring/roaring_test.go b/roaring/roaring_test.go index 210ffa94d..fe966be17 100644 --- a/roaring/roaring_test.go +++ b/roaring/roaring_test.go @@ -1113,14 +1113,15 @@ func testBitmapQuick(t *testing.T, n int, min, max uint64) { } // Remove all values in random order. - for _, i := range rand.Perm(len(a)) { - removed, _ := bm.Remove(a[i]) + for i, item := range rand.Perm(len(a)) { + removed, _ := bm.Remove(a[item]) if removed { manual_count-- } //check count if manual_count != bm.Count() { - t.Fatalf("expected bitmap Remove count to be: %d got: %d", manual_count, bm.Count()) + t.Fatalf("removing %d/%d [%d] from bitmap: expected bitmap Remove count to be %d, got %d", + i, len(a), a[item], manual_count, bm.Count()) } } diff --git a/row.go b/row.go index a2e938434..79c918311 100644 --- a/row.go +++ b/row.go @@ -57,6 +57,12 @@ func (r *Row) IsEmpty() bool { return true } +func (r *Row) Freeze() { + for _, s := range r.segments { + s.Freeze() + } +} + // Merge merges data from other into r. func (r *Row) Merge(other *Row) { var segments []rowSegment @@ -210,15 +216,6 @@ func (r *Row) SetBit(i uint64) (changed bool) { return r.createSegmentIfNotExists(i / ShardWidth).SetBit(i) } -// clearBit clears the i-th column of the row. -func (r *Row) clearBit(i uint64) (changed bool) { // nolint: unparam - s := r.segment(i / ShardWidth) - if s == nil { - return false - } - return s.ClearBit(i) -} - // Segments returns a list of all segments in the row. func (r *Row) Segments() []rowSegment { return r.segments @@ -246,12 +243,12 @@ func (r *Row) createSegmentIfNotExists(shard uint64) *rowSegment { } // Insert new segment. - r.segments = append(r.segments, rowSegment{data: *roaring.NewBitmap()}) + r.segments = append(r.segments, rowSegment{data: roaring.NewSliceBitmap()}) if i < len(r.segments) { copy(r.segments[i+1:], r.segments[i:]) } r.segments[i] = rowSegment{ - data: *roaring.NewBitmap(), + data: roaring.NewSliceBitmap(), shard: shard, writable: true, } @@ -312,13 +309,17 @@ type rowSegment struct { // Underlying raw bitmap implementation. // This is an mmapped bitmap if writable is false. Otherwise // it is a heap allocated bitmap which can be manipulated. - data roaring.Bitmap + data *roaring.Bitmap writable bool // Bit count n uint64 } +func (s *rowSegment) Freeze() { + s.data.Freeze() +} + // Merge adds chunks from other to s. // Chunks in s are overwritten if they exist in other. func (s *rowSegment) Merge(other *rowSegment) { @@ -332,50 +333,58 @@ func (s *rowSegment) Merge(other *rowSegment) { // IntersectionCount returns the number of intersections between s and other. func (s *rowSegment) IntersectionCount(other *rowSegment) uint64 { - return s.data.IntersectionCount(&other.data) + return s.data.IntersectionCount(other.data) } // Intersect returns the itersection of s and other. func (s *rowSegment) Intersect(other *rowSegment) *rowSegment { - data := s.data.Intersect(&other.data) + data := s.data.Intersect(other.data) + data.Freeze() return &rowSegment{ - data: *data, - shard: s.shard, - n: data.Count(), + data: data, + shard: s.shard, + n: data.Count(), + writable: true, } } // Union returns the bitwise union of s and other. func (s *rowSegment) Union(other *rowSegment) *rowSegment { - data := s.data.Union(&other.data) + data := s.data.Union(other.data) + data.Freeze() return &rowSegment{ - data: *data, - shard: s.shard, - n: data.Count(), + data: data, + shard: s.shard, + n: data.Count(), + writable: true, } } // Difference returns the diff of s and other. func (s *rowSegment) Difference(other *rowSegment) *rowSegment { - data := s.data.Difference(&other.data) + data := s.data.Difference(other.data) + data.Freeze() return &rowSegment{ - data: *data, - shard: s.shard, - n: data.Count(), + data: data, + shard: s.shard, + n: data.Count(), + writable: true, } } // Xor returns the xor of s and other. func (s *rowSegment) Xor(other *rowSegment) *rowSegment { - data := s.data.Xor(&other.data) + data := s.data.Xor(other.data) + data.Freeze() return &rowSegment{ - data: *data, - shard: s.shard, - n: data.Count(), + data: data, + shard: s.shard, + n: data.Count(), + writable: true, } } @@ -386,11 +395,13 @@ func (s *rowSegment) Shift() (*rowSegment, error) { if err != nil { return nil, errors.Wrap(err, "shifting roaring data") } + data.Freeze() return &rowSegment{ - data: *data, - shard: s.shard, - n: data.Count(), + data: data, + shard: s.shard, + n: data.Count(), + writable: true, }, nil } @@ -439,7 +450,11 @@ func (s *rowSegment) ensureWritable() { return } - s.data = *s.data.Clone() + // This doesn't actually clone all the containers, but does clone + // the bitmap itself -- we get a new bitmap, but it just marks the + // containers as frozen and shares them. It's now safe to write to + // this bitmap, but the actual containers are copy-on-write. + s.data = s.data.Freeze() s.writable = true }