diff --git a/bsi.go b/bsi.go index c8956e21e..afb7dd89a 100644 --- a/bsi.go +++ b/bsi.go @@ -14,29 +14,19 @@ 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. +// 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. @@ -98,23 +88,209 @@ 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. +// Values must be added sequentially. +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. +// Columns must be inserted sequentially, and duplicates are not allowed. +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/bsi_test.go b/bsi_test.go new file mode 100644 index 000000000..57803a7f9 --- /dev/null +++ b/bsi_test.go @@ -0,0 +1,175 @@ +// 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 ( + "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]) + } + } + }) + } +} 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 005f32523..9f4728a40 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 fe6d31f9d..b4de84573 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..1da117c56 --- /dev/null +++ b/roaring/add_test.go @@ -0,0 +1,416 @@ +// 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 ( + "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].DirectAdd(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].DirectAdd(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.