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.
This commit is contained in:
Nia Weiss 2020-11-16 14:53:41 -05:00
parent 47202b0ed2
commit 46818863e8
No known key found for this signature in database
GPG key ID: 895E83409BFDA1BB
11 changed files with 1846 additions and 106 deletions

237
bsi.go
View file

@ -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
}

View file

@ -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.

View file

@ -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) {

View file

@ -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.

3
go.mod
View file

@ -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

45
go.sum
View file

@ -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=

View file

@ -422,6 +422,8 @@ var callInfoByFunc = map[string]callInfo{
"_field": "",
"k": int64(0),
"filter": nil,
"from": nil,
"to": nil,
},
},

861
roaring/add.go Normal file
View file

@ -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)
}

400
roaring/add_test.go Normal file
View file

@ -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)
}
}
})
}
}

View file

@ -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.

View file

@ -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.