From 46818863e88822244a8f3a6aaa0e83e7538eb2ac Mon Sep 17 00:00:00 2001 From: Nia Weiss Date: Mon, 16 Nov 2020 14:53:41 -0500 Subject: [PATCH 1/6] implement TopK on time This replaces the former TopK BSI building algorithm, as the row cache was too expensive. Additionally, BSI addition has been optimized with specialized adders inside of roaring. --- bsi.go | 237 ++++++++-- executor.go | 305 ++++++++++++- executor_test.go | 31 +- fragment.go | 30 -- go.mod | 3 +- go.sum | 45 +- pql/ast.go | 2 + roaring/add.go | 861 +++++++++++++++++++++++++++++++++++++ roaring/add_test.go | 400 +++++++++++++++++ roaring/container_stash.go | 12 + roaring/roaring.go | 26 +- 11 files changed, 1846 insertions(+), 106 deletions(-) create mode 100644 roaring/add.go create mode 100644 roaring/add_test.go diff --git a/bsi.go b/bsi.go index c8956e21e..cd727e7a5 100644 --- a/bsi.go +++ b/bsi.go @@ -14,27 +14,15 @@ package pilosa -import "math/bits" +import ( + "math/bits" + + "github.com/pilosa/pilosa/v2/roaring" +) // bsiData contains BSI-structured data. type bsiData []*Row -// insert a value for a column in the BSI data. -func (bsi *bsiData) insert(column uint64, value uint64) { - data := *bsi - for value != 0 { - bit := bits.TrailingZeros64(value) - value &^= 1 << bit - - for len(data) <= bit { - data = append(data, NewRow()) - } - - data[bit].SetBit(column) - } - *bsi = data -} - // pivotDescending loops over nonzero BSI values in descending order. // For each value, the provided function is called with the value and a slice of the associated columns. func (bsi bsiData) pivotDescending(filter *Row, branch uint64, limit, offset *uint64, fn func(uint64, ...uint64)) { @@ -98,23 +86,208 @@ func (bsi bsiData) distribution(filter *Row) bsiData { } */ -// addBSI adds BSI values together. +var placeholderBitmap = roaring.NewBitmap() + +// addBSI adds two BSI bitmaps together. +// It does not handle sign and has no concept of overflow. func addBSI(x, y bsiData) bsiData { - if len(x) > len(y) { - x, y = y, x + // Accumulate row segments. + segments := make([][]rowSegment, len(x)+len(y)) + xsegs, ysegs := segments[:len(x)], segments[len(x):] + for i, r := range x { + xsegs[i] = r.segments } - carry := NewRow() - out := make(bsiData, 0, len(y)) - for i, v := range x { - out = append(out, v.Xor(y[i]).Xor(carry)) - carry = v.Intersect(y[i]).Union(v.Intersect(carry), y[i].Intersect(carry)) + for i, r := range y { + ysegs[i] = r.segments } - for _, v := range y[len(x):] { - out = append(out, v.Xor(carry)) - carry = v.Intersect(carry) + + var dst bsiData + var xbitmaps, ybitmaps []*roaring.Bitmap + for { + // Find the next shard. + next := ^uint64(0) + for _, s := range segments { + if len(s) == 0 { + continue + } + shard := s[0].shard + if shard < next { + next = shard + } + } + if next == ^uint64(0) { + // There are no remaining shards. + break + } + + // Accumulate bitmaps for this shard. + xbitmaps, ybitmaps = xbitmaps[:0], ybitmaps[:0] + for i, segs := range xsegs { + if len(segs) == 0 || segs[0].shard != next { + continue + } + xsegs[i] = segs[1:] + bm := segs[0].data + if !bm.Any() { + continue + } + for len(xbitmaps) < i { + xbitmaps = append(xbitmaps, placeholderBitmap) + } + xbitmaps = append(xbitmaps, bm) + } + for i, segs := range ysegs { + if len(segs) == 0 || segs[0].shard != next { + continue + } + ysegs[i] = segs[1:] + bm := segs[0].data + if !bm.Any() { + continue + } + for len(ybitmaps) < i { + ybitmaps = append(ybitmaps, placeholderBitmap) + } + ybitmaps = append(ybitmaps, bm) + } + + // Add the shard values together. + var out []*roaring.Bitmap + switch { + case len(xbitmaps) == 0: + // There are no values in x. + out = ybitmaps + case len(ybitmaps) == 0: + // There are no values in y. + out = xbitmaps + default: + out = roaring.Add(xbitmaps, ybitmaps) + } + + // Convert the bitmaps to output segments. + for i, b := range out { + if !b.Any() { + continue + } + for len(dst) <= i { + dst = append(dst, NewRow()) + } + dst[i].segments = append(dst[i].segments, rowSegment{ + shard: next, + writable: true, + data: b, + n: b.Count(), + }) + } } - if carry.Any() { - out = append(out, carry) - } - return out + + return dst +} + +// rowBuilder builds a row quickly from individual values. +// It is optimized for the case in which values are generated sequentially. +type rowBuilder struct { + bm *roaring.Bitmap + mask *[1024]uint64 + array []uint16 + key uint64 + n int32 +} + +// flushKey flushes the data at the current key to the bitmap. +func (b *rowBuilder) flushKey() { + var c *roaring.Container + switch { + case b.mask != nil: + c = roaring.NewContainerBitmapN(b.mask[:], b.n) + b.mask = nil + case len(b.array) > 0: + c = roaring.NewContainerArrayCopy(b.array) + b.array = b.array[:0] + default: + return + } + + if b.bm == nil { + b.bm = roaring.NewBitmap() + } + if old := b.bm.Containers.Get(b.key); old != nil { + c = roaring.Union(c, old) + } + b.bm.Containers.Put(b.key, c) +} + +// Add a value to the bitmap. +func (b *rowBuilder) Add(v uint64) { + vkey := v / (1 << 16) + if b.key != vkey { + // This is a new key, so flush the old one. + b.flushKey() + b.key = vkey + } + + if b.mask != nil { + // Add to the mask. + b.n += int32(1 &^ (b.mask[uint16(v)/64] >> (v % 64))) + b.mask[uint16(v)/64] |= 1 << (v % 64) + return + } + + // Add to an array. + b.array = append(b.array, uint16(v)) + if len(b.array) >= roaring.ArrayMaxSize { + // The array is too big. + // Convert it to a bitmask. + m := [1024]uint64{} + for _, v := range b.array { + m[v/64] |= 1 << (v % 64) + } + b.n = int32(len(b.array)) + b.array = b.array[:0] + b.mask = &m + } +} + +// Build a Row from stored data. +// This resets the builder. +func (b *rowBuilder) Build() *Row { + // Flush the active key to the bitmap. + b.flushKey() + + // Remove the bitmap and convert it to a Row. + bm := b.bm + b.bm = nil + if bm == nil { + return NewRow() + } + return NewRowFromBitmap(bm) +} + +// bsiBuilder assembles BSI data. +// It is optimized for the case in which values are generated sequentially. +type bsiBuilder []rowBuilder + +// Insert a value into the BSI data. +// It is assumed that it did not previously exist. +func (b *bsiBuilder) Insert(col, val uint64) { + for val != 0 { + i := bits.TrailingZeros64(val) + val &^= 1 << i + for len(*b) <= i { + *b = append(*b, rowBuilder{}) + } + (*b)[i].Add(col) + } +} + +// Build BSI data. +// This resets the builder. +func (b *bsiBuilder) Build() bsiData { + builders := *b + *b = builders[:0] + rows := make(bsiData, len(builders)) + for i := range builders { + rows[i] = builders[i].Build() + } + return rows } diff --git a/executor.go b/executor.go index f17c65e55..641d8866a 100644 --- a/executor.go +++ b/executor.go @@ -1875,6 +1875,7 @@ func (e *executor) executeTopK(ctx context.Context, qcx *Qcx, index string, c *p }, nil } +// executeTopKShard builds a perpendicular BSI bitmap of a shard for TopK. func (e *executor) executeTopKShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64) (_ []*Row, err0 error) { span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopKShard") defer span.Finish() @@ -1897,6 +1898,22 @@ func (e *executor) executeTopKShard(ctx context.Context, qcx *Qcx, index string, return nil, ErrFieldNotFound } + // Parse "from" time, if set. + var fromTime time.Time + if v, ok := c.Args["from"]; ok { + if fromTime, err = parseTime(v); err != nil { + return nil, errors.Wrap(err, "parsing from time") + } + } + + // Parse "to" time, if set. + var toTime time.Time + if v, ok := c.Args["to"]; ok { + if toTime, err = parseTime(v); err != nil { + return nil, errors.Wrap(err, "parsing to time") + } + } + // Fetch the filter. var filterBitmap *Row if filter, hasFilter, err := c.CallArg("filter"); err != nil { @@ -1919,13 +1936,19 @@ func (e *executor) executeTopKShard(ctx context.Context, qcx *Qcx, index string, ftype := f.Type() switch ftype { - case FieldTypeSet, FieldTypeTime: + case FieldTypeTime: + if !(fromTime.IsZero() && toTime.IsZero()) { + return e.executeTopKShardTime(ctx, tx, filterBitmap, index, fieldName, shard, fromTime, toTime) + } + fallthrough + case FieldTypeSet: return e.executeTopKShardSet(ctx, tx, filterBitmap, index, fieldName, shard) default: return nil, errors.Errorf("field type %q is not yet supported by TopK", ftype) } } +// executeTopKShardSet builds a perpendicular BSI bitmap of a set field within a shard. func (e *executor) executeTopKShardSet(ctx context.Context, tx Tx, filter *Row, index, field string, shard uint64) ([]*Row, error) { span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeTopKShardSet") defer span.Finish() @@ -1935,7 +1958,285 @@ func (e *executor) executeTopKShardSet(ctx context.Context, tx Tx, filter *Row, return nil, nil } - return f.cardinalityBSISet(ctx, tx, filter) + return topKFragments(ctx, tx, filter, f) +} + +// executeTopKShardTime builds a perpendicular BSI bitmap of a time field within a shard. +func (e *executor) executeTopKShardTime(ctx context.Context, tx Tx, filter *Row, index, field string, shard uint64, from, to time.Time) ([]*Row, error) { + // Fetch index. + idx := e.Holder.Index(index) + if idx == nil { + return nil, newNotFoundError(ErrIndexNotFound, index) + } + + // Fetch field. + f := idx.Field(field) + if f == nil { + return nil, newNotFoundError(ErrFieldNotFound, field) + } + + // Check the time quantum. + quantum := f.TimeQuantum() + if quantum == "" { + // ???????? + return nil, nil + } + + // Fetch fragments. + var fragments []*fragment + for _, view := range viewsByTimeRange(viewStandard, from, to, quantum) { + f := e.Holder.fragment(index, field, view, shard) + if f == nil { + continue + } + + fragments = append(fragments, f) + } + + return topKFragments(ctx, tx, filter, fragments...) +} + +// topKFragments builds a perpendicular BSI bitmap from fragments. +// The fragments are expected to be from set fields. +func topKFragments(ctx context.Context, tx Tx, filter *Row, fragments ...*fragment) (bsiData, error) { + // Acquire fragment container iterators. + iters := make([]roaring.ContainerIterator, len(fragments)) + for i, f := range fragments { + f.mu.RLock() + defer f.mu.RUnlock() + + iter, _, err := tx.ContainerIterator(f.index, f.field, f.view, f.shard, 0) + if err != nil { + return nil, err + } + + iters[i] = iter + } + + // Merge to a single container iterator. + var it roaring.ContainerIterator + if len(iters) == 1 { + it = iters[0] + } else { + it = mergerate(iters...) + } + + // Extract filter data if a filter was provided. + var filterData *topKFilter + if filter != nil { + var f topKFilter + f.fill(filter) + filterData = &f + } + + return doTopK(ctx, it, filterData) +} + +// mergerate returns a container iterator that unions many container iterators. +func mergerate(iters ...roaring.ContainerIterator) *mergerator { + iterStates := make([]mergeState, len(iters)) + for i, s := range iters { + iterStates[i].iter = s + } + m := mergerator{ + iters: iterStates, + heap: make(mergeratorHeap, 0, len(iters)), + } + for i := range iterStates { + m.pusherate(uint64(i)) + } + return &m +} + +// mergerator is a container iterator that merges container iterators (via unioning). +type mergerator struct { + iters []mergeState + heap mergeratorHeap + container *roaring.Container + key uint64 +} + +// pusherate pushes the iterator at the given index back onto the heap. +func (m *mergerator) pusherate(idx uint64) { + state := &m.iters[idx] + it := state.iter + if !it.Next() { + it.Close() + return + } + key, c := it.Value() + state.c = c + m.heap.push(mergeNode{ + key: key, + idx: idx, + }) +} + +func (m *mergerator) Next() bool { + nodes := m.heap.pop() + if len(nodes) == 0 { + return false + } + key := nodes[0].key + var container *roaring.Container + for _, n := range nodes { + c := m.iters[n.idx].c + if container != nil { + container = roaring.Union(container, c) + } else { + container = c + } + m.pusherate(n.idx) + } + m.key, m.container = key, container + return true +} + +func (m *mergerator) Value() (uint64, *roaring.Container) { + return m.key, m.container +} + +func (m *mergerator) Close() { + for _, n := range m.heap { + m.iters[n.idx].iter.Close() + } + m.heap = nil +} + +type mergeState struct { + c *roaring.Container + iter roaring.ContainerIterator +} + +// mergeratorHeap is a binary min-heap over keys. +// This is used to find the next iterator to hit. +type mergeratorHeap []mergeNode + +type mergeNode struct { + key, idx uint64 +} + +// push a node onto the heap. +func (h *mergeratorHeap) push(node mergeNode) { + s := *h + i := len(s) + s = append(s, node) + for i != 0 && s[(i-1)/2].key > s[i].key { + s[(i-1)/2], s[i] = s[i], s[(i-1)/2] + i = (i - 1) / 2 + } + *h = s +} + +// pop the minimum key off of the heap. +// If there are multiple iterators with this keys, this returns all of them. +func (h *mergeratorHeap) pop() []mergeNode { + s := *h + if len(s) == 0 { + return nil + } + + n := 0 + for key := s[0].key; len(s) > n && s[0].key == key; n++ { + s[0], s[len(s)-n-1] = s[len(s)-n-1], s[0] + s[:len(s)-n-1].minHeapify() + } + + *h = s[:len(s)-n] + return s[len(s)-n:] +} + +// minHeapify fixes the heap invariant after updating the heap's root. +func (h mergeratorHeap) minHeapify() { + i := 0 + for { + l, r := 2*i+1, 2*i+2 + min := i + if l < len(h) && h[l].key < h[min].key { + min = l + } + if r < len(h) && h[r].key < h[min].key { + min = r + } + if min == i { + return + } + h[min], h[i] = h[i], h[min] + i = min + } +} + +// doTopK uses a raw Pilosa matrix to produce a perpendicular BSI bitmap. +// It will apply a row filter if one is provided. +func doTopK(ctx context.Context, it roaring.ContainerIterator, filter *topKFilter) (bsiData, error) { + row := ^uint64(0) + var count uint64 + + var builder bsiBuilder + var i uint16 + for it.Next() { + if i == 0 { + if err := ctx.Err(); err != nil { + return nil, err + } + } + i++ + + // Fetch the next container. + key, container := it.Value() + keyrow, subkey := key/(ShardWidth>>16), key%(ShardWidth>>16) + if keyrow != row { + // The previous row has ended. + // Flush the count to the BSI data. + builder.Insert(row, count) + row, count = keyrow, 0 + } + + // Add the selected bits to the count. + if filter != nil { + fc := filter[subkey] + if fc == nil { + continue + } + count += uint64(roaring.IntersectionCount(container, fc)) + } else { + count += uint64(container.N()) + } + } + + // Add the final count to the BSI data. + builder.Insert(row, count) + + // Construct the result. + return builder.Build(), nil +} + +// topKFilter is a row filter for a TopK query. +// It is represented as a contiguous array of containers. +type topKFilter [ShardWidth >> 16]*roaring.Container + +// fill the filter with the contents of a Row. +func (f *topKFilter) fill(row *Row) { + for _, s := range row.segments { + it, _ := s.data.Containers.Iterator(0) + f.fillIt(it) + } + // I don't think multiple segments make sense here? +} + +func (f *topKFilter) fillIt(it roaring.ContainerIterator) { + defer it.Close() + + for it.Next() { + key, c := it.Value() + + key %= uint64(len(f)) + + if f[key] != nil { + panic("duplicate container in topk filter") + } + f[key] = c + } } // executeTopN executes a TopN() call. diff --git a/executor_test.go b/executor_test.go index 9dd3053a1..3d8cddd7f 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1063,7 +1063,7 @@ func TestExecutor_Execute_SetRowAttrs(t *testing.T) { }) } -func TestExecutor_Execute_TopK(t *testing.T) { +func TestExecutor_Execute_TopK_Set(t *testing.T) { c := test.MustRunCluster(t, 2) defer c.Close() @@ -1094,6 +1094,35 @@ func TestExecutor_Execute_TopK(t *testing.T) { } } +func TestExecutor_Execute_TopK_Time(t *testing.T) { + c := test.MustRunCluster(t, 2) + defer c.Close() + + // Load some test data into a time field. + c.CreateField(t, "i", pilosa.IndexOptions{TrackExistence: true}, "f", pilosa.OptFieldTypeTime("YMD", true)) + c.Query(t, "i", ` + Set(0, f=0, 2016-01-02T00:00) + Set(0, f=1, 2016-01-02T00:00) + Set(0, f=0, 2016-01-03T00:00) + Set(1, f=0, 2016-01-10T00:00) + Set(100000000, f=2, 2016-02-02T00:00) + Set(200000000, f=3, 2015-01-02T00:00) + `) + + // Execute query. + if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `TopK(f, k=3, from=2016-01-01T00:00, to=2016-01-11T00:00)`}); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result.Results, []interface{}{&pilosa.PairsField{ + Pairs: []pilosa.Pair{ + {ID: 0, Count: 2}, + {ID: 1, Count: 1}, + }, + Field: "f", + }}) { + t.Fatalf("unexpected result: %s", spew.Sdump(result)) + } +} + // Ensure a TopN() query can be executed. func TestExecutor_Execute_TopN(t *testing.T) { t.Run("RowIDColumnID", func(t *testing.T) { diff --git a/fragment.go b/fragment.go index f7af84160..c9d147402 100644 --- a/fragment.go +++ b/fragment.go @@ -1775,36 +1775,6 @@ func (f *fragment) forEachBit(tx Tx, fn func(rowID, columnID uint64) error) erro }) } -// cardinalityBSISet constructs a perpendicular BSI bitmap containing the cardinality of each specified row in a set field. -func (f *fragment) cardinalityBSISet(ctx context.Context, tx Tx, filter *Row) ([]*Row, error) { - f.mu.Lock() - defer f.mu.Unlock() - - // Fetch row IDs. - rowIDs, err := f.unprotectedRows(ctx, tx, 0) - if err != nil { - return nil, err - } - - // Count the bits in each row. - var out bsiData - for _, id := range rowIDs { - row, err := f.unprotectedRow(tx, id) - if err != nil { - return nil, err - } - var count uint64 - if filter != nil { - count = row.intersectionCount(filter) - } else { - count = row.Count() - } - out.insert(id, count) - } - - return out, nil -} - // top returns the top rows from the fragment. // If opt.Src is specified then only rows which intersect src are returned. // If opt.FilterValues exist then the row attribute specified by field is matched. diff --git a/go.mod b/go.mod index ed42aac5f..e633a3046 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,8 @@ require ( github.com/uber/jaeger-lib v2.2.0+incompatible // indirect github.com/zeebo/blake3 v0.0.4 go.etcd.io/bbolt v1.3.5 - golang.org/x/mod v0.3.0 + golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 + golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 golang.org/x/text v0.3.3 // indirect diff --git a/go.sum b/go.sum index ea9c803a4..ab592f284 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CAFxX/gcnotifier v0.0.0-20190112062741-224a280d589d h1:n0G4ckjMEj7bWuGYUX0i8YlBeBBJuZ+HEHvHfyBDZtI= github.com/CAFxX/gcnotifier v0.0.0-20190112062741-224a280d589d/go.mod h1:Rn2zM2MnHze07LwkneP48TWt6UiZhzQTwCvw6djVGfE= github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 h1:dmc/C8bpE5VkQn65PNbbyACDC8xw8Hpp/NEurdPmQDQ= @@ -56,6 +58,7 @@ github.com/glycerine/idem v0.0.0-20190127113923-7a8083893311 h1:AAXH0ZvYIHHqU06A github.com/glycerine/idem v0.0.0-20190127113923-7a8083893311/go.mod h1:B72P/ZM99sNiCmaQJflpmMAF5LsDzStpLdWzn0+Vr2Y= github.com/glycerine/lmdb-go v1.9.34 h1:0lymJjpdelYnIMcNzsKROfIaApt99zhaHtjDJTHjGkE= github.com/glycerine/lmdb-go v1.9.34/go.mod h1:DrPeeTGooMg6B7cjNSP14perptTJzzdBy5YoosthrRs= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -70,19 +73,14 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -94,9 +92,7 @@ github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -127,7 +123,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -162,7 +157,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pilosa/memberlist v0.1.4-0.20190415211605-f6512523c021 h1:ERLyN4p3KS5Fk2ADsDENm2cq0+Lx6sF1sG8uwRlySpU= github.com/pilosa/memberlist v0.1.4-0.20190415211605-f6512523c021/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -173,9 +167,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -209,10 +201,8 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+D github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -224,7 +214,6 @@ github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -232,7 +221,6 @@ github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= @@ -254,7 +242,6 @@ github.com/zeebo/blake3 v0.0.4 h1:vtZ4X8B2lKXZFg2Xyg6Wo36mvmnJvc2VQYTtA4RDCkI= github.com/zeebo/blake3 v0.0.4/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny2i34= github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05 h1:4pW5fMvVkrgkMXdvIsVRRTs69DWYA8uNNQsu1stfVKU= github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05/go.mod h1:Gr+78ptB0MwXxm//LBaEvBiaXY7hXJ6KGe2V32X2F6E= -go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -265,21 +252,25 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 h1:2/QncOxxpPAdiH+E00abYw/SaQG353gltz79Nl1zrYE= +golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519 h1:x6rhz8Y9CjbgQkccRGmELH6K+LJj7tOoh3XWeC1yaQM= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -287,16 +278,13 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -306,14 +294,14 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -324,17 +312,15 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e h1:aZzprAO9/8oim3qStq3wc1Xuxx4QmAGriC4VU4ojemQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -345,7 +331,6 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= diff --git a/pql/ast.go b/pql/ast.go index 87a8b49d4..50b38e5c5 100644 --- a/pql/ast.go +++ b/pql/ast.go @@ -422,6 +422,8 @@ var callInfoByFunc = map[string]callInfo{ "_field": "", "k": int64(0), "filter": nil, + "from": nil, + "to": nil, }, }, diff --git a/roaring/add.go b/roaring/add.go new file mode 100644 index 000000000..db15eee47 --- /dev/null +++ b/roaring/add.go @@ -0,0 +1,861 @@ +// Copyright 2020 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package roaring + +import ( + "fmt" + "math/bits" + "unsafe" +) + +// Add two BSI bitmaps producing a new BSI bitmap. +func Add(x, y []*Bitmap) []*Bitmap { + // Collect iterators. + type itNode struct { + it ContainerIterator + c *Container + key uint64 + done bool + } + xits := make([]itNode, len(x)) + for i, b := range x { + it, _ := b.Containers.Iterator(0) + defer it.Close() + + node := &xits[i] + + node.it = it + if !it.Next() { + node.done = true + continue + } + + node.key, node.c = it.Value() + } + yits := make([]itNode, len(y)) + for i, b := range y { + it, _ := b.Containers.Iterator(0) + defer it.Close() + + node := &yits[i] + + node.it = it + if !it.Next() { + node.done = true + continue + } + + node.key, node.c = it.Value() + } + + bits := len(x) + if len(y) > len(x) { + bits = len(y) + } + + var carryRing [2]carryBuffer + var temp [1024]uint64 + var dst []*Bitmap + for { + key := ^uint64(0) + for i := range xits { + if xits[i].done { + continue + } + + k := xits[i].key + if k < key { + key = k + } + } + for i := range yits { + if yits[i].done { + continue + } + + k := yits[i].key + if k < key { + key = k + } + } + if key == ^uint64(0) { + break + } + + carryRing[0].clear() + for i := 0; i <= bits; i++ { + var x, y *Container + if i < len(xits) && xits[i].key == key && !xits[i].done { + x = xits[i].c + if xits[i].it.Next() { + xits[i].key, xits[i].c = xits[i].it.Value() + } else { + xits[i].done = true + } + } + if i < len(yits) && yits[i].key == key && !yits[i].done { + y = yits[i].c + if yits[i].it.Next() { + yits[i].key, yits[i].c = yits[i].it.Value() + } else { + yits[i].done = true + } + } + + c := fullAddContainers(x, y, &carryRing[i%2], &carryRing[1-(i%2)], &temp) + if c == nil { + continue + } + + for i >= len(dst) { + dst = append(dst, NewBitmap()) + } + dst[i].Containers.Put(key, c) + } + } + + return dst +} + +// fullAddContainers implements the bitwise formula for a 3-input-2-output full adder. +// Any of the 3 inputs may be nil, in which case they are treated as zeroes. +// The carry is written to carryOut, which must not be nil. +// The temp buffer will be used to store intermediate values, and can be safely stack-allocated. +func fullAddContainers(x, y *Container, carryIn, carryOut *carryBuffer, temp *[1024]uint64) *Container { + if roaringParanoia { + x.CheckN() + y.CheckN() + carryIn.check() + defer carryOut.check() + } + + // Accumulate inputs. + var xm, ym, zm *[1024]uint64 + var xa, ya, za []uint16 + switch x.typ() { + case ContainerNil: + case ContainerBitmap: + xm = x.bitmask() + case ContainerArray: + xa = x.array() + case ContainerRun: + // Create a temporary mask on the stack. + // This should not happen very often. + var mask [1024]uint64 + for _, r := range x.runs() { + splatRun(&mask, r) + } + xm = &mask + default: + panic("invalid container type") + } + switch y.typ() { + case ContainerNil: + case ContainerBitmap: + ym = y.bitmask() + case ContainerArray: + ya = y.array() + case ContainerRun: + // Create a temporary mask on the stack. + // This should not happen very often. + var mask [1024]uint64 + for _, r := range y.runs() { + splatRun(&mask, r) + } + ym = &mask + default: + panic("invalid container type") + } + if carryIn != nil && carryIn.count > 0 { + if carryIn.isBitmap { + zm = carryIn.bitmap() + } else { + za = carryIn.array()[:carryIn.count] + } + } + + // Handle each of the 27 possible mask/array/nil combinations. + switch { + case xm != nil: + dst, carry := temp, carryOut.bitmap() + var do, co uint32 + switch { + case ym != nil: + switch { + case zm != nil: + do, co = addMaskMaskMaskToMask(dst, carry, xm, ym, zm) + case len(za) > 0: + do, co = addArrayMaskMaskToMask(dst, carry, za, xm, ym) + default: + do, co = addMaskMaskToMask(dst, carry, xm, ym) + } + case len(ya) > 0: + switch { + case zm != nil: + do, co = addArrayMaskMaskToMask(dst, carry, ya, xm, zm) + case len(za) > 0: + do, co = addArrayArrayMaskToMask(dst, carry, ya, za, xm, uint32(x.N())) + default: + do, co = addArrayArrayMaskToMask(dst, carry, ya, nil, xm, uint32(x.N())) + } + default: + switch { + case zm != nil: + do, co = addMaskMaskToMask(dst, carry, xm, zm) + case len(za) > 0: + do, co = addArrayArrayMaskToMask(dst, carry, za, nil, xm, uint32(x.N())) + default: + carryOut.clear() + return x + } + } + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + return NewContainerBitmapN(append([]uint64(nil), dst[:]...), int32(do)) + case len(xa) > 0: + switch { + case ym != nil: + dst, carry := temp, carryOut.bitmap() + var do, co uint32 + switch { + case zm != nil: + do, co = addArrayMaskMaskToMask(dst, carry, xa, ym, zm) + case len(za) > 0: + do, co = addArrayArrayMaskToMask(dst, carry, xa, za, ym, uint32(y.N())) + default: + do, co = addArrayArrayMaskToMask(dst, carry, xa, nil, ym, uint32(y.N())) + } + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), dst[:]...), int32(do)) + case len(ya) > 0: + switch { + case zm != nil: + do, co := addArrayArrayMaskToMask(temp, carryOut.bitmap(), xa, ya, zm, carryIn.count) + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), temp[:]...), int32(do)) + case len(za) > 0: + if do, co := addArrayArrayArrayToArray((*[4096]uint16)(unsafe.Pointer(temp)), carryOut.array(), xa, ya, za); do|co != ^uint16(0) { + carryOut.isBitmap = false + carryOut.count = uint32(co) + if do == 0 { + return nil + } + return NewContainerArrayCopy((*[4096]uint16)(unsafe.Pointer(temp))[:do]) + } + + do, co := addArrayArrayArrayToMask(temp, carryOut.bitmap(), xa, ya, za) + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), temp[:]...), int32(do)) + default: + if do, co := addArrayArrayToArray((*[4096]uint16)(unsafe.Pointer(temp)), carryOut.array(), xa, ya); do|co != ^uint16(0) { + carryOut.isBitmap = false + carryOut.count = uint32(co) + if do == 0 { + return nil + } + return NewContainerArrayCopy((*[4096]uint16)(unsafe.Pointer(temp))[:do]) + } + + do, co := addArrayArrayArrayToMask(temp, carryOut.bitmap(), xa, ya, nil) + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), temp[:]...), int32(do)) + } + default: + switch { + case zm != nil: + do, co := addArrayArrayMaskToMask(temp, carryOut.bitmap(), xa, nil, zm, carryIn.count) + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), temp[:]...), int32(do)) + case len(za) > 0: + if do, co := addArrayArrayToArray((*[4096]uint16)(unsafe.Pointer(temp)), carryOut.array(), xa, za); do|co != ^uint16(0) { + carryOut.isBitmap = false + carryOut.count = uint32(co) + if do == 0 { + return nil + } + return NewContainerArrayCopy((*[4096]uint16)(unsafe.Pointer(temp))[:do]) + } + + do, co := addArrayArrayArrayToMask(temp, carryOut.bitmap(), xa, za, nil) + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), temp[:]...), int32(do)) + default: + carryOut.clear() + return x + } + } + default: + switch { + case ym != nil: + dst, carry := temp, carryOut.bitmap() + var do, co uint32 + switch { + case zm != nil: + do, co = addMaskMaskToMask(dst, carry, ym, zm) + case len(za) > 0: + do, co = addArrayArrayMaskToMask(dst, carry, za, nil, ym, uint32(y.N())) + default: + carryOut.clear() + return y + } + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), dst[:]...), int32(do)) + case len(ya) > 0: + switch { + case zm != nil: + do, co := addArrayArrayMaskToMask(temp, carryOut.bitmap(), ya, nil, zm, carryIn.count) + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), temp[:]...), int32(do)) + case len(za) > 0: + if do, co := addArrayArrayToArray((*[4096]uint16)(unsafe.Pointer(temp)), carryOut.array(), ya, za); do|co != ^uint16(0) { + carryOut.isBitmap = false + carryOut.count = uint32(co) + if do == 0 { + return nil + } + return NewContainerArrayCopy((*[4096]uint16)(unsafe.Pointer(temp))[:do]) + } + + do, co := addArrayArrayArrayToMask(temp, carryOut.bitmap(), ya, za, nil) + if co > 0 { + carryOut.isBitmap = true + carryOut.count = co + } else { + carryOut.clear() + } + if do == 0 { + return nil + } + return NewContainerBitmapN(append([]uint64(nil), temp[:]...), int32(do)) + default: + carryOut.clear() + return y + } + default: + carryOut.clear() + return carryIn.containerize() + } + } +} + +// carryBuffer is a buffer used to store carry bits. +// It is designed to be stack-allocated. +// As such **IT SHOULD NOT BE POOLED**. +type carryBuffer struct { + isBitmap bool + count uint32 + data [1024]uint64 +} + +func (b *carryBuffer) bitmap() *[1024]uint64 { + return &b.data +} + +func (b *carryBuffer) array() *[4096]uint16 { + return (*[4096]uint16)(unsafe.Pointer(&b.data)) +} + +// compact converts the buffer to an array if it would be more efficient. +func (b *carryBuffer) compact() { + if b.isBitmap && b.count < 4096 { + b.compactSlow() + } +} + +func (b *carryBuffer) compactSlow() { + var buf [4096]uint16 + i := 0 + for j, v := range b.data { + for v != 0 { + k := bits.TrailingZeros64(v) + v &^= 1 << k + buf[i] = 64*uint16(j) + uint16(k) + i++ + } + } + copy(b.array()[:], buf[:i]) + b.isBitmap = false + if roaringParanoia { + b.check() + } +} + +// clear the buffer, making it effectively full of zeroes. +func (b *carryBuffer) clear() { + b.isBitmap = false + b.count = 0 +} + +// check that invariants hold. +// This exists mainly for debugging. +func (b *carryBuffer) check() { + if b.isBitmap { + var count int + for _, v := range b.bitmap() { + count += bits.OnesCount64(v) + } + if uint32(count) != b.count { + panic(fmt.Errorf("count mismatch: reported %d but got %d", b.count, count)) + } + } else { + if b.count > uint32(len(b.array())) { + panic(fmt.Errorf("found too many bits: %d of a max of %d", b.count, len(b.array()))) + } + if b.count > 0 { + arr := b.array()[:b.count] + for i, v := range arr { + if i > 0 && v <= arr[i-1] { + panic(fmt.Errorf("broken array: %d after %d", v, arr[i-1])) + } + } + } + } +} + +// containerize the contents of the buffer. +func (b *carryBuffer) containerize() *Container { + if b.count == 0 { + return nil + } + + b.compact() + + if !b.isBitmap { + return NewContainerArray(append([]uint16(nil), b.array()[:b.count]...)) + } + + return NewContainerBitmapN(append([]uint64(nil), b.bitmap()[:]...), int32(b.count)) +} + +// addArrayArrayToArray implemnents a half-adder over two arrays, producing array outputs. +// If the results are too big, this returns ^uint16(0) to indicate that the operation failed. +func addArrayArrayToArray(dst, carry *[4096]uint16, x, y []uint16) (uint16, uint16) { + _, _ = &dst[0], &carry[0] + + // Half-add x and y. + i, j, do, co := 0, 0, 0, 0 + for i < len(x) && j < len(y) { + a, b := x[i], y[j] + switch { + case a < b: + // Copy all values under b to the lower output bit. + for ; i < len(x) && x[i] < b; i++ { + if do >= len(dst) { + return ^uint16(0), ^uint16(0) + } + + dst[do] = x[i] + do++ + } + case b < a: + // Copy all values under a to the lower output bit. + for ; j < len(y) && y[j] < a; j++ { + if do >= len(dst) { + return ^uint16(0), ^uint16(0) + } + + dst[do] = y[j] + do++ + } + default: + // Copy the value to the carry. + if co >= len(carry) { + return ^uint16(0), ^uint16(0) + } + carry[co] = a + co++ + i++ + j++ + } + } + + // Copy the remaining data to the lower output bit. + var remaining []uint16 + switch { + case i < len(x): + remaining = x[i:] + case j < len(y): + remaining = y[j:] + } + if do+len(remaining) > len(dst) { + return ^uint16(0), ^uint16(0) + } + copy(dst[do:], remaining) + return uint16(do + len(remaining)), uint16(co) +} + +// addArrayArrayArrayToArray implemnents a full-adder over three arrays, producing array outputs. +// If the results are too big, this returns ^uint16(0) to indicate that the operation failed. +func addArrayArrayArrayToArray(dst, carry *[4096]uint16, x, y, z []uint16) (uint16, uint16) { + _, _ = &dst[0], &carry[0] + + // Run a full adder. + i, j, k, do, co := 0, 0, 0, 0, 0 + for i < len(x) && j < len(y) && k < len(z) { + a, b, c := x[i], y[j], z[k] + switch { + case a < b && a < c: + // Find the lowest value in the other two inputs. + next := b + if c < b { + next = c + } + + // Copy every value below that to the lower output bit. + for ; i < len(x) && x[i] < next; i++ { + if do >= len(dst) { + return ^uint16(0), ^uint16(0) + } + + dst[do] = x[i] + do++ + } + case b < a && b < c: + // Find the lowest value in the other two inputs. + next := a + if c < a { + next = c + } + + // Copy every value below that to the lower output bit. + for ; j < len(y) && y[j] < next; j++ { + if do >= len(dst) { + return ^uint16(0), ^uint16(0) + } + + dst[do] = y[j] + do++ + } + case c < a && c < b: + // Find the lowest value in the other two inputs. + next := a + if b < a { + next = b + } + + // Copy every value below that to the lower output bit. + for ; k < len(z) && z[k] < next; k++ { + if do >= len(dst) { + return ^uint16(0), ^uint16(0) + } + + dst[do] = z[k] + do++ + } + + case co >= len(carry): + // At least two bits are set, so a carry bit will be produced. + return ^uint16(0), ^uint16(0) + case a == b && b != c: + // Carry the lower value (a/b). + carry[co] = a + co++ + i++ + j++ + case b == c && a != b: + // Carry the lower value (b/c). + carry[co] = b + co++ + j++ + k++ + case a == c && a != b: + // Carry the lower value (a/c). + carry[co] = a + co++ + i++ + k++ + + case do >= len(dst): + // All three inputs are set, so this will produce both a lower bit and a carry. + return ^uint16(0), ^uint16(0) + default: + // a == b == c; 1+1+1 = 0b11 + dst[do] = a + do++ + carry[co] = a + co++ + i++ + j++ + k++ + } + } + + // Run a half adder. + if k < len(z) { + // Re-order the inputs such that the inputs (if any) which still have data are x and y. + if i < len(x) { + j, y = k, z + } else { + i, x = k, z + } + } + for i < len(x) && j < len(y) { + a, b := x[i], y[j] + switch { + case a < b: + // Copy all values under b to the lower output bit. + for ; i < len(x) && x[i] < b; i++ { + if do >= len(dst) { + return ^uint16(0), ^uint16(0) + } + + dst[do] = x[i] + do++ + } + case b < a: + // Copy all values under a to the lower output bit. + for ; j < len(y) && y[j] < a; j++ { + if do >= len(dst) { + return ^uint16(0), ^uint16(0) + } + + dst[do] = y[j] + do++ + } + default: + // Copy the value to the carry. + if co >= len(carry) { + return ^uint16(0), ^uint16(0) + } + carry[co] = a + co++ + i++ + j++ + } + } + + // Copy the remaining data to the lower output bit. + var remaining []uint16 + switch { + case i < len(x): + remaining = x[i:] + case j < len(y): + remaining = y[j:] + } + if do+len(remaining) > len(dst) { + return ^uint16(0), ^uint16(0) + } + copy(dst[do:], remaining) + return uint16(do + len(remaining)), uint16(co) +} + +// addArrayArrayArrayToMask implemnents a full-adder over three arrays, producing bitmask outputs. +// This is generally only needed when addArrayArrayArrayToArray fails. +func addArrayArrayArrayToMask(dst, carry *[1024]uint64, x, y, z []uint16) (uint32, uint32) { + // Start with blank outputs. + *dst = [1024]uint64{} + *carry = [1024]uint64{} + + var co uint64 + for _, v := range x { + // Add each value to the lower output bit. + dst[v/64] |= 1 << (v % 64) + + // There is no carry because this is the first copy of the output. + } + for _, v := range y { + // Increment the carry output counter if the value is already included. + co += (dst[v/64] >> (v % 64)) & 1 + + // Insert the carry bit if the value is already in the lower output bit. + carry[v/64] |= dst[v/64] & (1 << (v % 64)) + + // Flip the lower output bit for the value. + dst[v/64] ^= 1 << (v % 64) + } + for _, v := range z { + // Increment the carry output counter if the value is already included. + co += (dst[v/64] >> (v % 64)) & 1 + + // Insert the carry bit if the value is already in the lower output bit. + carry[v/64] |= dst[v/64] & (1 << (v % 64)) + + // Flip the lower output bit for the value. + dst[v/64] ^= 1 << (v % 64) + } + + // If there were no collisions, the number of values in the lower output bit would be equal to the sum of the counts of the inputs. + // For each carry, we subtract 2 as it was produced by combining two copies of a value. + return uint32(len(x)+len(y)+len(z)) - 2*uint32(co), uint32(co) +} + +// addArrayArrayMaskToMask implemnents a full-adder over two arrays and one bitmask, producing bitmask outputs. +func addArrayArrayMaskToMask(dst, carry *[1024]uint64, x, y []uint16, z *[1024]uint64, zc uint32) (uint32, uint32) { + // Clear the carry output. + *carry = [1024]uint64{} + + // Copy the mask input to the lower output bit. + *dst = *z + + var co uint64 + for _, v := range x { + // Increment the carry output counter if the value is already included. + co += (dst[v/64] >> (v % 64)) & 1 + + // Insert the carry bit if the value is already in the lower output bit. + carry[v/64] |= dst[v/64] & (1 << (v % 64)) + + // Flip the lower output bit for the value. + dst[v/64] ^= 1 << (v % 64) + } + for _, v := range y { + // Increment the carry output counter if the value is already included. + co += (dst[v/64] >> (v % 64)) & 1 + + // Insert the carry bit if the value is already in the lower output bit. + carry[v/64] |= dst[v/64] & (1 << (v % 64)) + + // Flip the lower output bit for the value. + dst[v/64] ^= 1 << (v % 64) + } + + // If there were no collisions, the number of values in the lower output bit would be equal to the sum of the counts of the inputs. + // For each carry, we subtract 2 as it was produced by combining two copies of a value. + return uint32(len(x)+len(y)) + zc - 2*uint32(co), uint32(co) +} + +// addArrayArrayMaskToMask implemnents a full-adder over one array and two bitmasks, producing bitmask outputs. +func addArrayMaskMaskToMask(dst, carry *[1024]uint64, x []uint16, y, z *[1024]uint64) (uint32, uint32) { + _, _, _, _ = &dst[0], &carry[0], &y[0], &z[0] + + // Do a bitwise combine of y and z into dst and carry. + var doi, coi int + for i := range dst { + yv, zv := y[i], z[i] + dstv, carryv := yv^zv, yv&zv + dst[i], carry[i] = dstv, carryv + doi += bits.OnesCount64(dstv) + coi += bits.OnesCount64(carryv) + } + + // Add the array values. + var newco uint64 + for _, v := range x { + // Increment the carry output counter if the value is already included. + newco += (dst[v/64] >> (v % 64)) & 1 + + // Insert the carry bit if the value is already in the lower output bit. + carry[v/64] |= dst[v/64] & (1 << (v % 64)) + + // Flip the lower output bit for the value. + dst[v/64] ^= 1 << (v % 64) + } + + // If there were no collisions, the number of values in the lower output bit would be equal to the sum of the counts of the inputs. + // For each carry, we subtract 2 as it was produced by combining two copies of a value. + return uint32(doi) + uint32(len(x)) - 2*uint32(newco), uint32(coi) + uint32(newco) +} + +// addMaskMaskToMask implemnents a half-adder over two bitmasks, producing bitmask outputs. +func addMaskMaskToMask(dst, carry *[1024]uint64, x, y *[1024]uint64) (uint32, uint32) { + _, _, _, _ = &dst[0], &carry[0], &x[0], &y[0] + + // Do a bitwise combine of both inputs into dst and carry. + var do, co int + for i := range dst { + xv, yv := x[i], y[i] + dstv, carryv := xv^yv, xv&yv + dst[i], carry[i] = dstv, carryv + do += bits.OnesCount64(dstv) + co += bits.OnesCount64(carryv) + } + + return uint32(do), uint32(co) +} + +// addMaskMaskMaskToMask implemnents a full-adder over three bitmasks, producing bitmask outputs. +func addMaskMaskMaskToMask(dst, carry *[1024]uint64, x, y, z *[1024]uint64) (uint32, uint32) { + _, _, _, _, _ = &dst[0], &carry[0], &x[0], &y[0], &z[0] + + // Do a bitwise combine of all three inputs into dst and carry. + var do, co int + for i := range dst { + xv, yv, zv := x[i], y[i], z[i] + dstv, carryv := xv^yv^zv, (xv&yv)|(yv&zv)|(xv&zv) + dst[i], carry[i] = dstv, carryv + do += bits.OnesCount64(dstv) + co += bits.OnesCount64(carryv) + } + + return uint32(do), uint32(co) +} diff --git a/roaring/add_test.go b/roaring/add_test.go new file mode 100644 index 000000000..da75b7847 --- /dev/null +++ b/roaring/add_test.go @@ -0,0 +1,400 @@ +package roaring + +import ( + "fmt" + "math/bits" + "testing" + + "golang.org/x/exp/rand" +) + +func randomMask(dst *[1024]uint64, seed uint64) uint32 { + _ = &dst[0] + + var src rand.PCGSource + src.Seed(seed) + var n uint32 + for i := range dst { + v := src.Uint64() + n += uint32(bits.OnesCount64(v)) + dst[i] = src.Uint64() + } + return n +} + +func randomArray(arr *[4096]uint16, mask *[1024]uint64, seed uint64) []uint16 { + _, _ = &arr[0], &mask[0] + + var src rand.PCGSource + src.Seed(seed) + + *mask = [1024]uint64{} + + nv := src.Uint64() + n := (nv % 4096) >> ((nv / 4096) % 10) + for i := uint64(0); i < n; i++ { + v := uint16(src.Uint64()) + mask[v/64] |= 1 << (v % 64) + } + + x := arr[:0] + for i, v := range mask { + for v != 0 { + j := bits.TrailingZeros64(v) + v &^= 1 << j + x = append(x, 64*uint16(i)+uint16(j)) + } + } + + return x +} + +func splatArray(arr ...uint16) (dst [1024]uint64) { + for _, v := range arr { + dst[v/64] |= 1 << (v % 64) + } + return +} + +func diffMask(t *testing.T, in string, expect, got *[1024]uint64) bool { + t.Helper() + + n := 0 + for i := range expect { + expv, gotv := expect[i], got[i] + unexpected, missing := gotv&^expv, expv&^gotv + for unexpected != 0 { + j := bits.TrailingZeros64(unexpected) + unexpected &^= 1 << j + t.Errorf("unexpected value %d in %s", 64*i+j, in) + } + for missing != 0 { + j := bits.TrailingZeros64(missing) + missing &^= 1 << j + t.Errorf("missing value %d in %s", 64*i+j, in) + } + n += bits.OnesCount64(unexpected | missing) + if n > 20 { + t.Errorf("too many errors in %s", in) + break + } + } + return n > 0 +} + +// TestAddInternal randomly tests the full and half adder logic in Add. +func TestAddInternal(t *testing.T) { + t.Parallel() + + testCount := 2000 + if testing.Short() { + testCount = 20 + } + + t.Run("addArrayArrayToArray", func(t *testing.T) { + t.Parallel() + + var src rand.PCGSource + src.Seed(1) + seedup := make(map[uint64]struct{}) + + for i := 0; i < testCount; i++ { + genSeed: + seed := src.Uint64() + if _, dup := seedup[seed]; dup { + goto genSeed + } + seedup[seed] = struct{}{} + t.Run(fmt.Sprint(seed), func(t *testing.T) { + t.Parallel() + tries := 0 + var xmask, ymask [1024]uint64 + var xarr, yarr [4096]uint16 + var gotdst, gotcarry [4096]uint16 + try: + x := randomArray(&xarr, &xmask, seed) + y := randomArray(&yarr, &ymask, 3*seed) + do, co := addArrayArrayToArray(&gotdst, &gotcarry, x, y) + if do|co == ^uint16(0) { + if tries > 100 { + t.Fatal("repeatedly failing") + } + seed++ + tries++ + goto try + } + var dstmask, dstcarry [1024]uint64 + expdo, expco := addMaskMaskToMask(&dstmask, &dstcarry, &xmask, &ymask) + if uint32(expdo) != expdo || uint32(expco) != expco { + t.Errorf("expected %d/%d out but got %d/%d out", expdo, expco, do, co) + } + gotdstmask, gotcarrymask := splatArray(gotdst[:do]...), splatArray(gotcarry[:co]...) + dstfail := diffMask(t, "lower bit", &dstmask, &gotdstmask) + carryfail := diffMask(t, "carry bit", &dstcarry, &gotcarrymask) + if dstfail || carryfail { + t.Log(x, y) + } + }) + } + }) + + t.Run("addArrayArrayArrayToArray", func(t *testing.T) { + t.Parallel() + + var src rand.PCGSource + src.Seed(2) + seedup := make(map[uint64]struct{}) + + for i := 0; i < testCount; i++ { + genSeed: + seed := src.Uint64() + if _, dup := seedup[seed]; dup { + goto genSeed + } + seedup[seed] = struct{}{} + t.Run(fmt.Sprint(seed), func(t *testing.T) { + t.Parallel() + + tries := 0 + var xmask, ymask, zmask [1024]uint64 + var xarr, yarr, zarr [4096]uint16 + var gotdst, gotcarry [4096]uint16 + try: + x := randomArray(&xarr, &xmask, seed) + y := randomArray(&yarr, &ymask, 3*seed) + z := randomArray(&zarr, &zmask, 5*seed) + do, co := addArrayArrayArrayToArray(&gotdst, &gotcarry, x, y, z) + if do|co == ^uint16(0) { + if tries > 100 { + t.Fatal("repeatedly failing") + } + seed++ + tries++ + goto try + } + var dstmask, dstcarry [1024]uint64 + expdo, expco := addMaskMaskMaskToMask(&dstmask, &dstcarry, &xmask, &ymask, &zmask) + if uint32(expdo) != expdo || uint32(expco) != expco { + t.Errorf("expected %d/%d out but got %d/%d out", expdo, expco, do, co) + } + gotdstmask, gotcarrymask := splatArray(gotdst[:do]...), splatArray(gotcarry[:co]...) + dstfail := diffMask(t, "lower bit", &dstmask, &gotdstmask) + carryfail := diffMask(t, "carry bit", &dstcarry, &gotcarrymask) + if dstfail || carryfail { + t.Log(x, y, z) + } + }) + } + }) + + t.Run("addArrayArrayArrayToMask", func(t *testing.T) { + t.Parallel() + + var src rand.PCGSource + src.Seed(3) + seedup := make(map[uint64]struct{}) + + for i := 0; i < testCount; i++ { + genSeed: + seed := src.Uint64() + if _, dup := seedup[seed]; dup { + goto genSeed + } + seedup[seed] = struct{}{} + t.Run(fmt.Sprint(seed), func(t *testing.T) { + t.Parallel() + + var xmask, ymask, zmask [1024]uint64 + var xarr, yarr, zarr [4096]uint16 + x := randomArray(&xarr, &xmask, seed) + y := randomArray(&yarr, &ymask, 3*seed) + z := randomArray(&zarr, &zmask, 5*seed) + var gotdst, gotcarry [1024]uint64 + do, co := addArrayArrayArrayToMask(&gotdst, &gotcarry, x, y, z) + var dstmask, dstcarry [1024]uint64 + expdo, expco := addMaskMaskMaskToMask(&dstmask, &dstcarry, &xmask, &ymask, &zmask) + if uint32(expdo) != expdo || uint32(expco) != expco { + t.Errorf("expected %d/%d out but got %d/%d out", expdo, expco, do, co) + } + dstfail := diffMask(t, "lower bit", &dstmask, &gotdst) + carryfail := diffMask(t, "carry bit", &dstcarry, &gotcarry) + if dstfail || carryfail { + t.Log(x, y, z) + } + }) + } + }) + + t.Run("addArrayArrayMaskToMask", func(t *testing.T) { + t.Parallel() + + var src rand.PCGSource + src.Seed(4) + seedup := make(map[uint64]struct{}) + + for i := 0; i < testCount; i++ { + genSeed: + seed := src.Uint64() + if _, dup := seedup[seed]; dup { + goto genSeed + } + seedup[seed] = struct{}{} + t.Run(fmt.Sprint(seed), func(t *testing.T) { + t.Parallel() + + var xmask, ymask, z [1024]uint64 + var xarr, yarr [4096]uint16 + x := randomArray(&xarr, &xmask, seed) + y := randomArray(&yarr, &ymask, 3*seed) + zc := randomMask(&z, 5*seed) + var gotdst, gotcarry [1024]uint64 + do, co := addArrayArrayMaskToMask(&gotdst, &gotcarry, x, y, &z, zc) + var dstmask, dstcarry [1024]uint64 + expdo, expco := addMaskMaskMaskToMask(&dstmask, &dstcarry, &xmask, &ymask, &z) + if uint32(expdo) != expdo || uint32(expco) != expco { + t.Errorf("expected %d/%d out but got %d/%d out", expdo, expco, do, co) + } + dstfail := diffMask(t, "lower bit", &dstmask, &gotdst) + carryfail := diffMask(t, "carry bit", &dstcarry, &gotcarry) + if dstfail || carryfail { + t.Log(x, y, z) + } + }) + } + }) + + t.Run("addArrayMaskMaskToMask", func(t *testing.T) { + t.Parallel() + + var src rand.PCGSource + src.Seed(5) + seedup := make(map[uint64]struct{}) + + for i := 0; i < testCount; i++ { + genSeed: + seed := src.Uint64() + if _, dup := seedup[seed]; dup { + goto genSeed + } + seedup[seed] = struct{}{} + t.Run(fmt.Sprint(seed), func(t *testing.T) { + t.Parallel() + + var xmask, y, z [1024]uint64 + var xarr [4096]uint16 + x := randomArray(&xarr, &xmask, seed) + randomMask(&y, 3*seed) + randomMask(&z, 5*seed) + var gotdst, gotcarry [1024]uint64 + do, co := addArrayMaskMaskToMask(&gotdst, &gotcarry, x, &y, &z) + var dstmask, dstcarry [1024]uint64 + expdo, expco := addMaskMaskMaskToMask(&dstmask, &dstcarry, &xmask, &y, &z) + if uint32(expdo) != expdo || uint32(expco) != expco { + t.Errorf("expected %d/%d out but got %d/%d out", expdo, expco, do, co) + } + dstfail := diffMask(t, "lower bit", &dstmask, &gotdst) + carryfail := diffMask(t, "carry bit", &dstcarry, &gotcarry) + if dstfail || carryfail { + t.Log(x, y, z) + } + }) + } + }) +} + +// TestAdd randomly tests addition logic. +func TestAdd(t *testing.T) { + t.Parallel() + + var src rand.PCGSource + src.Seed(8) + seedup := make(map[uint64]struct{}) + + for i := 0; i < 20; i++ { + genSeed: + seed := src.Uint64() + if _, dup := seedup[seed]; dup { + goto genSeed + } + seedup[seed] = struct{}{} + t.Run(fmt.Sprint(seed), func(t *testing.T) { + t.Parallel() + + var pcg rand.PCGSource + pcg.Seed(seed) + rnd := rand.New(&pcg) + numzipf := rand.NewZipf(rnd, 1.5, 2, 1<<20) + countzipf := rand.NewZipf(rnd, 1.3, 7, 1<<44) + + var x, y []*Bitmap + xvals, yvals := map[uint64]uint64{}, map[uint64]uint64{} + for n := numzipf.Uint64(); n > 0; n-- { + idx := pcg.Uint64() % (1 << 20) + xvals[idx] = countzipf.Uint64() + yvals[idx] = countzipf.Uint64() + } + for xn := numzipf.Uint64(); uint64(len(xvals)) < xn; { + xvals[pcg.Uint64()%(1<<20)] = countzipf.Uint64() + } + for yn := numzipf.Uint64(); uint64(len(yvals)) < yn; { + yvals[pcg.Uint64()%(1<<20)] = countzipf.Uint64() + } + + expectSums := map[uint64]uint64{} + for k, v := range xvals { + if v == 0 { + delete(xvals, k) + continue + } + expectSums[k] = v + for v != 0 { + i := bits.TrailingZeros64(v) + v &^= 1 << i + for len(x) <= i { + x = append(x, NewBitmap()) + } + x[i].Add(k) + } + } + for k, v := range yvals { + if v == 0 { + delete(xvals, k) + continue + } + expectSums[k] += v + for v != 0 { + i := bits.TrailingZeros64(v) + v &^= 1 << i + for len(y) <= i { + y = append(y, NewBitmap()) + } + y[i].Add(k) + } + } + + sumsBSI := Add(x, y) + gotSums := make(map[uint64]uint64, len(expectSums)) + for i, b := range sumsBSI { + mask := uint64(1) << i + for _, k := range b.Slice() { + gotSums[k] |= mask + } + } + for k, v := range gotSums { + if _, ok := expectSums[k]; !ok { + t.Errorf("unexpected sum of %d for %d", v, k) + } + } + for k, v := range expectSums { + got, ok := gotSums[k] + if !ok { + t.Errorf("missing sum of %d for %d", v, k) + continue + } + if got != v { + t.Errorf("sum for %d differs: expected %d but got %d", k, v, got) + } + } + }) + } +} diff --git a/roaring/container_stash.go b/roaring/container_stash.go index f6a9f8f05..8a959fdde 100644 --- a/roaring/container_stash.go +++ b/roaring/container_stash.go @@ -472,6 +472,18 @@ func (c *Container) bitmap() []uint64 { return (*[1024]uint64)(unsafe.Pointer(c.pointer))[:] } +func (c *Container) bitmask() *[1024]uint64 { + if c == nil { + panic("attempt to read nil container's bitmap") + } + if roaringParanoia { + if c.typeID != ContainerBitmap { + panic("attempt to read non-bitmap's bitmap") + } + } + return (*[1024]uint64)(unsafe.Pointer(c.pointer)) +} + // AsBitmap yields a 65k-bit bitmap, storing it in the target if a target // is provided. The target should be zeroed, or this becomes an implicit // union. diff --git a/roaring/roaring.go b/roaring/roaring.go index 793c7b93c..587cc5f7b 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -4473,17 +4473,18 @@ func intersectBitmapBitmap(a, b *Container) *Container { // local variables added to prevent BCE checks in loop // see https://go101.org/article/bounds-check-elimination.html var ( - ab = a.bitmap()[:bitmapN] - bb = b.bitmap()[:bitmapN] - ob = make([]uint64, bitmapN) + ab = a.bitmask() + bb = b.bitmask() + ob = [1024]uint64{} n int32 ) - for i := 0; i < bitmapN; i++ { + _, _ = &ab[0], &bb[0] + for i := range ob { ob[i] = ab[i] & bb[i] n += int32(popcount(ob[i])) } - output := NewContainerBitmapN(ob, n) + output := NewContainerBitmapN(ob[:], n) return output } @@ -5668,19 +5669,20 @@ func xorBitmapBitmap(a, b *Container) *Container { // see https://go101.org/article/bounds-check-elimination.html var ( - ab = a.bitmap()[:bitmapN] - bb = b.bitmap()[:bitmapN] - ob = make([]uint64, bitmapN)[:bitmapN] + ab = a.bitmask() + bb = b.bitmask() + ob = [1024]uint64{} n int32 ) - for i := 0; i < bitmapN; i++ { + _, _ = &ab[0], &bb[0] + for i := range ob { ob[i] = ab[i] ^ bb[i] n += int32(popcount(ob[i])) } - output := NewContainerBitmapN(ob, n) + output := NewContainerBitmapN(ob[:], n) if n < ArrayMaxSize { output = output.bitmapToArray() } @@ -7115,6 +7117,10 @@ func Difference(a, b *Container) *Container { return difference(a, b) } +func IntersectionCount(x, y *Container) int32 { + return intersectionCount(x, y) +} + // Add yields a container identical to c, but with the given bit set; added // is true if the bit wasn't previously set. It is unspecified whether // the original container is modified. From 64b83137a073edbe5e9272dcd755f5e8fa7d108d Mon Sep 17 00:00:00 2001 From: Matthew Jaffee Date: Fri, 20 Nov 2020 08:23:55 -0700 Subject: [PATCH 2/6] add tests for addBSI --- bsi.go | 4 +- bsi_test.go | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 bsi_test.go diff --git a/bsi.go b/bsi.go index cd727e7a5..308de8e0b 100644 --- a/bsi.go +++ b/bsi.go @@ -24,7 +24,9 @@ import ( type bsiData []*Row // pivotDescending loops over nonzero BSI values in descending order. -// For each value, the provided function is called with the value and a slice of the associated columns. +// For each value, the provided function is called with the value and +// a slice of the associated columns. TODO, document branch and +// limit/offset behavior (particularly when nil). func (bsi bsiData) pivotDescending(filter *Row, branch uint64, limit, offset *uint64, fn func(uint64, ...uint64)) { // This "pivot" algorithm works by treating the BSI data as a tree. // Each branch of this tree corresponds to a power-of-2-sized range of BSI values. diff --git a/bsi_test.go b/bsi_test.go new file mode 100644 index 000000000..43245359f --- /dev/null +++ b/bsi_test.go @@ -0,0 +1,161 @@ +package pilosa + +import ( + "fmt" + "math/rand" + "sort" + "testing" +) + +// TestBSIAdd does a number of iterations. For each iteration, it +// generates a random number of ids, and two random values for each id +// to add together. +func TestBSIAdd(t *testing.T) { + // TODO wouldn't it be cool if our test suite had a randomized + // burn-in mode where you could run any test which supported it + // with a random seed and way more iterations? + rnd := rand.New(rand.NewSource(99)) + //numZipf := rand.NewZipf(rnd, 1.5, 2, ShardWidth-1) + idZipf := rand.NewZipf(rnd, 1.8, 4, ShardWidth) + var builderA, builderB bsiBuilder + // a and b are generated slices of numbers to add together + var a, b []uint64 + // idToIndex maps record ids to indexes in a and b + idToIndex := make(map[int]int) + // indexToID has the record id for each value in a and b + indexToID := []uint64{} + + min := 999999999 + max := 0 + + for iteration := 0; iteration < 1; iteration++ { + t.Run(fmt.Sprintf("%d", iteration), func(t *testing.T) { + // reset generated data + a, b = a[:0], b[:0] + indexToID = indexToID[:0] + for k := range idToIndex { + delete(idToIndex, k) + } + + // z generates the values, they can be fairly large, but are usually small + z := rand.NewZipf(rnd, 1.3, 7, 1<<44) + id := -1 + for i := 0; true; i++ { + // get the next id, skipping a random amount + id = id + int(idZipf.Uint64()+1) + if id >= ShardWidth { + if i < min { + min = i + } + if max < i { + max = i + } + t.Log("num: ", i) + break + } + idToIndex[id] = int(i) + indexToID = append(indexToID, uint64(id)) + + // append a random value to each data slice + a = append(a, z.Uint64()) + b = append(b, z.Uint64()) + } + + // build the BSIs based on the data slices and generated IDs + for index, id := range indexToID { + va, vb := a[index], b[index] + builderA.Insert(uint64(id), va) + builderB.Insert(uint64(id), vb) + } + dataA, dataB := builderA.Build(), builderB.Build() + dataC := addBSI(dataA, dataB) + + // build results from added bsiData; results[i] should hold a[i]+b[i] + results := make([]uint64, len(a)) + dataC.pivotDescending(NewRow().Union(dataC...), 0, nil, nil, func(count uint64, ids ...uint64) { + for _, id := range ids { + results[idToIndex[int(id)]] = count + } + }) + + for i, res := range results { + if res != a[i]+b[i] { + t.Errorf("Mismatch at %d\na: %v\nb: %v\nr: %v", i, a, b, results) + } + } + }) + } + t.Log("min", min) + t.Log("max", max) +} + +type bsiAddCase struct { + positions []uint64 + a []uint64 + b []uint64 +} + +func (b bsiAddCase) Len() int { + return len(b.positions) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (b bsiAddCase) Less(i, j int) bool { + return b.positions[i] < b.positions[j] +} + +// Swap swaps the elements with indexes i and j. +func (b bsiAddCase) Swap(i, j int) { + b.positions[i], b.positions[j] = b.positions[j], b.positions[i] + b.a[i], b.a[j] = b.a[j], b.a[i] + b.b[i], b.b[j] = b.b[j], b.b[i] +} + +// TestBSIAddCases tests specific cases of bsiAdd (would generally be +// pulled from randomly generated ones from TestBSIAdd upon failure). +func TestBSIAddCases(t *testing.T) { + tests := []bsiAddCase{ + { + positions: []uint64{161311, 611110, 82544, 996022, 836077, 64964, 480737, 156534, 240525, 580896, 239236, 54607, 1019438, 894260, 17570, 884645, 936658, 682651, 987695, 390274}, + a: []uint64{17, 1, 2846, 45437619, 23781, 36, 88, 168691, 13417, 1301, 10, 71, 0, 176, 1010, 21, 1, 509, 17, 4}, + b: []uint64{24, 288, 12737, 14, 150, 21, 24, 354, 0, 19, 5, 150, 3940, 121, 25, 621, 7, 9023592401, 6033, 7}, + }, + { + positions: []uint64{17570, 54607}, + a: []uint64{1010, 71}, + b: []uint64{25, 150}, + }, + } + + var builderA, builderB bsiBuilder + for i, tst := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + if len(tst.a) != len(tst.b) || len(tst.a) != len(tst.positions) { + t.Fatalf("Malformed test, a is %d, but b is %d", len(tst.a), len(tst.b)) + } + sort.Sort(tst) + + for i := 0; i < len(tst.a); i++ { + builderA.Insert(tst.positions[i], tst.a[i]) + builderB.Insert(tst.positions[i], tst.b[i]) + } + + dataA, dataB := builderA.Build(), builderB.Build() + dataC := addBSI(dataA, dataB) + // maps id to count + results := make(map[uint64]uint64) + dataC.pivotDescending(NewRow().Union(dataC...), 0, nil, nil, func(count uint64, ids ...uint64) { + for _, id := range ids { + results[id] = count + } + }) + + for i, id := range tst.positions { + if results[id] != tst.a[i]+tst.b[i] { + t.Fatalf("value %d mismatch, id: %d. got %d, want %d", i, id, results[id], tst.a[i]+tst.b[i]) + } + } + }) + } +} From 5d2c42b7bf5c02a20cc715c9960c9bc1b5e7c78f Mon Sep 17 00:00:00 2001 From: Nia Weiss Date: Fri, 20 Nov 2020 11:13:44 -0500 Subject: [PATCH 3/6] address documentation todo in pivotDescending --- bsi.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bsi.go b/bsi.go index 308de8e0b..7d76ec3c8 100644 --- a/bsi.go +++ b/bsi.go @@ -24,9 +24,9 @@ import ( type bsiData []*Row // pivotDescending loops over nonzero BSI values in descending order. -// For each value, the provided function is called with the value and -// a slice of the associated columns. TODO, document branch and -// limit/offset behavior (particularly when nil). +// For each value, the provided function is called with the value and a slice of the associated columns. +// If limit or offset are not-nil, they will be applied. +// Applying a limit or offset may modify the pointed-to value. func (bsi bsiData) pivotDescending(filter *Row, branch uint64, limit, offset *uint64, fn func(uint64, ...uint64)) { // This "pivot" algorithm works by treating the BSI data as a tree. // Each branch of this tree corresponds to a power-of-2-sized range of BSI values. From f9c196e4188534247d107745060793c3cafeb2a1 Mon Sep 17 00:00:00 2001 From: Nia Weiss Date: Fri, 20 Nov 2020 12:12:24 -0500 Subject: [PATCH 4/6] skip roaring add test in race Otherwise it overloads the race detector. --- roaring/add_test.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/roaring/add_test.go b/roaring/add_test.go index da75b7847..1da117c56 100644 --- a/roaring/add_test.go +++ b/roaring/add_test.go @@ -1,3 +1,19 @@ +// Copyright 2020 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !race + package roaring import ( @@ -353,7 +369,7 @@ func TestAdd(t *testing.T) { for len(x) <= i { x = append(x, NewBitmap()) } - x[i].Add(k) + x[i].DirectAdd(k) } } for k, v := range yvals { @@ -368,7 +384,7 @@ func TestAdd(t *testing.T) { for len(y) <= i { y = append(y, NewBitmap()) } - y[i].Add(k) + y[i].DirectAdd(k) } } From 410a372351f95e46ee6d14458e81cdae544cec92 Mon Sep 17 00:00:00 2001 From: Nia Weiss Date: Fri, 20 Nov 2020 12:38:53 -0500 Subject: [PATCH 5/6] clarify bsi-building preconditions --- bsi.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bsi.go b/bsi.go index 7d76ec3c8..afb7dd89a 100644 --- a/bsi.go +++ b/bsi.go @@ -220,6 +220,7 @@ func (b *rowBuilder) flushKey() { } // Add a value to the bitmap. +// Values must be added sequentially. func (b *rowBuilder) Add(v uint64) { vkey := v / (1 << 16) if b.key != vkey { @@ -270,7 +271,7 @@ func (b *rowBuilder) Build() *Row { type bsiBuilder []rowBuilder // Insert a value into the BSI data. -// It is assumed that it did not previously exist. +// Columns must be inserted sequentially, and duplicates are not allowed. func (b *bsiBuilder) Insert(col, val uint64) { for val != 0 { i := bits.TrailingZeros64(val) From d7b95689605ecc4ebcd896aefb40d336efde6583 Mon Sep 17 00:00:00 2001 From: Nia Weiss Date: Fri, 20 Nov 2020 12:40:28 -0500 Subject: [PATCH 6/6] add licesnse header to bsi_test.go --- bsi_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bsi_test.go b/bsi_test.go index 43245359f..57803a7f9 100644 --- a/bsi_test.go +++ b/bsi_test.go @@ -1,3 +1,17 @@ +// Copyright 2020 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package pilosa import (