featurebase/roaring/roaring.go
Seebs d4364bea52 slightly streamline array/array comparison
The net effect of this is to not recompute "the current
value of the first array" on every loop, pretty much.
However, the swap to make sure the inner loop is on the
longer array seems to be significant for performance.
On my system, this moves runtime from ~29us per op
to ~17us per op.
2018-11-16 14:59:46 -06:00

3640 lines
87 KiB
Go

// Copyright 2017 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 implements roaring bitmaps with support for incremental changes.
package roaring
import (
"encoding/binary"
"fmt"
"hash/fnv"
"io"
"math/bits"
"sort"
"unsafe"
"github.com/pkg/errors"
)
const (
// magicNumber is an identifier, in bytes 0-1 of the file.
magicNumber = uint32(12348)
// storageVersion indicates the storage version, in bytes 2-3.
storageVersion = uint32(0)
// cookie is the first four bytes in a roaring bitmap file,
// formed by joining magicNumber and storageVersion
cookie = magicNumber + storageVersion<<16
// headerBaseSize is the size in bytes of the cookie and key count at the
// beginning of a file.
headerBaseSize = 4 + 4
// runCountHeaderSize is the size in bytes of the run count stored
// at the beginning of every serialized run container.
runCountHeaderSize = 2
// interval16Size is the size of a single run in a container.runs.
interval16Size = 4
// bitmapN is the number of values in a container.bitmap.
bitmapN = (1 << 16) / 64
//containerArray indicates a container of bit position values
containerArray = byte(1)
//containerBitmap indicates a container of bits packed in a uint64 array block
containerBitmap = byte(2)
//containerRun indicates a container of run encoded bits
containerRun = byte(3)
maxContainerVal = 0xffff
)
type Containers interface {
// Get returns nil if the key does not exist.
Get(key uint64) *Container
// Put adds the container at key.
Put(key uint64, c *Container)
// PutContainerValues updates an existing container at key.
// If a container does not exist for key, a new one is allocated.
// TODO(2.0) make n int32
PutContainerValues(key uint64, containerType byte, n int, mapped bool)
// Remove takes the container at key out.
Remove(key uint64)
// GetOrCreate returns the container at key, creating a new empty container if necessary.
GetOrCreate(key uint64) *Container
// Clone does a deep copy of Containers, including cloning all containers contained.
Clone() Containers
// Last returns the highest key and associated container.
Last() (key uint64, c *Container)
// Size returns the number of containers stored.
Size() int
// Iterator returns a Contiterator which after a call to Next(), a call to Value() will
// return the first container at or after key. found will be true if a
// container is found at key.
Iterator(key uint64) (citer ContainerIterator, found bool)
Count() uint64
//Reset will clear the containers collection to allow for recycling during snapshot
Reset()
}
type ContainerIterator interface {
Next() bool
Value() (uint64, *Container)
}
// Bitmap represents a roaring bitmap.
type Bitmap struct {
Containers Containers
// Number of operations written to the writer.
opN int
// Writer where operations are appended to.
OpWriter io.Writer
}
// NewBitmap returns a Bitmap with an initial set of values.
func NewBitmap(a ...uint64) *Bitmap {
b := &Bitmap{
Containers: newSliceContainers(),
}
b.Add(a...)
return b
}
// NewFileBitmap returns a Bitmap with an initial set of values, used for file storage.
// By default, this is a copy of NewBitmap, but is replaced with B+Tree in server/enterprise.go
var NewFileBitmap func(a ...uint64) *Bitmap = NewBitmap
// Clone returns a heap allocated copy of the bitmap.
// Note: The OpWriter IS NOT copied to the new bitmap.
func (b *Bitmap) Clone() *Bitmap {
if b == nil {
return nil
}
// Create a copy of the bitmap structure.
other := &Bitmap{
Containers: b.Containers.Clone(),
}
return other
}
// Add adds values to the bitmap.
func (b *Bitmap) Add(a ...uint64) (changed bool, err error) {
changed = false
for _, v := range a {
// Create an add operation.
op := &op{typ: opTypeAdd, value: v}
// Write operation to op log.
if err := b.writeOp(op); err != nil {
return false, err
}
// Apply to the in-memory bitmap.
if op.apply(b) {
changed = true
}
}
return changed, nil
}
// DirectAdd adds a value to the bitmap by bypassing the op log.
func (b *Bitmap) DirectAdd(v uint64) bool {
cont := b.Containers.GetOrCreate(highbits(v))
return cont.add(lowbits(v))
}
// Contains returns true if v is in the bitmap.
func (b *Bitmap) Contains(v uint64) bool {
c := b.Containers.Get(highbits(v))
if c == nil {
return false
}
return c.Contains(lowbits(v))
}
// Remove removes values from the bitmap.
func (b *Bitmap) Remove(a ...uint64) (changed bool, err error) {
changed = false
for _, v := range a {
// Create an add operation.
op := &op{typ: opTypeRemove, value: v}
// Write operation to op log.
if err := b.writeOp(op); err != nil {
return false, err
}
// Apply operation to the bitmap.
if op.apply(b) {
changed = true
}
}
return changed, nil
}
func (b *Bitmap) remove(v uint64) bool {
c := b.Containers.Get(highbits(v))
if c == nil {
return false
}
// TODO - do nil check inside c.remove?
return c.remove(lowbits(v))
}
// Max returns the highest value in the bitmap.
// Returns zero if the bitmap is empty.
func (b *Bitmap) Max() uint64 {
if b.Containers.Size() == 0 {
return 0
}
hb, c := b.Containers.Last()
lb := c.max()
return hb<<16 | uint64(lb)
}
// Count returns the number of bits set in the bitmap.
func (b *Bitmap) Count() (n uint64) {
return b.Containers.Count()
}
// CountRange returns the number of bits set between [start, end).
func (b *Bitmap) CountRange(start, end uint64) (n uint64) {
if b.Containers.Size() == 0 {
return
}
skey := highbits(start)
ekey := highbits(end)
citer, found := b.Containers.Iterator(highbits(start))
// If range is entirely in one container then just count that range.
if found && skey == ekey {
citer.Next()
_, c := citer.Value()
return uint64(c.countRange(int32(lowbits(start)), int32(lowbits(end))))
}
for citer.Next() {
k, c := citer.Value()
if k < skey {
// TODO remove once we've validated this stuff works
panic("should be impossible for k to be less than skey")
}
if k == skey {
n += uint64(c.countRange(int32(lowbits(start)), maxContainerVal+1))
continue
}
if k < ekey {
n += uint64(c.n)
continue
}
if k == ekey {
n += uint64(c.countRange(0, int32(lowbits(end))))
break
}
if k > ekey {
break
}
}
return n
}
// Slice returns a slice of all integers in the bitmap.
func (b *Bitmap) Slice() []uint64 {
var a []uint64
itr := b.Iterator()
itr.Seek(0)
for v, eof := itr.Next(); !eof; v, eof = itr.Next() {
a = append(a, v)
}
return a
}
// SliceRange returns a slice of integers between [start, end).
func (b *Bitmap) SliceRange(start, end uint64) []uint64 {
var a []uint64
itr := b.Iterator()
itr.Seek(start)
for v, eof := itr.Next(); !eof && v < end; v, eof = itr.Next() {
a = append(a, v)
}
return a
}
// ForEach executes fn for each value in the bitmap.
func (b *Bitmap) ForEach(fn func(uint64)) {
itr := b.Iterator()
itr.Seek(0)
for v, eof := itr.Next(); !eof; v, eof = itr.Next() {
fn(v)
}
}
// ForEachRange executes fn for each value in the bitmap between [start, end).
func (b *Bitmap) ForEachRange(start, end uint64, fn func(uint64)) {
itr := b.Iterator()
itr.Seek(start)
for v, eof := itr.Next(); !eof && v < end; v, eof = itr.Next() {
fn(v)
}
}
// OffsetRange returns a new bitmap with a containers offset by start.
func (b *Bitmap) OffsetRange(offset, start, end uint64) *Bitmap {
if lowbits(offset) != 0 {
panic("offset must not contain low bits")
}
if lowbits(start) != 0 {
panic("range start must not contain low bits")
}
if lowbits(end) != 0 {
panic("range end must not contain low bits")
}
off := highbits(offset)
hi0, hi1 := highbits(start), highbits(end)
citer, _ := b.Containers.Iterator(hi0)
other := NewBitmap()
for citer.Next() {
k, c := citer.Value()
if k >= hi1 {
break
}
other.Containers.Put(off+(k-hi0), c)
}
return other
}
// container returns the container with the given key.
func (b *Bitmap) container(key uint64) *Container {
return b.Containers.Get(key)
}
// IntersectionCount returns the number of set bits that would result in an
// intersection between b and other. It is more efficient than actually
// intersecting the two and counting the result.
func (b *Bitmap) IntersectionCount(other *Bitmap) uint64 {
var n uint64
iiter, _ := b.Containers.Iterator(0)
jiter, _ := other.Containers.Iterator(0)
i, j := iiter.Next(), jiter.Next()
ki, ci := iiter.Value()
kj, cj := jiter.Value()
for i && j {
if ki < kj {
i = iiter.Next()
ki, ci = iiter.Value()
} else if ki > kj {
j = jiter.Next()
kj, cj = jiter.Value()
} else {
n += uint64(intersectionCount(ci, cj))
i, j = iiter.Next(), jiter.Next()
ki, ci = iiter.Value()
kj, cj = jiter.Value()
}
}
return n
}
// Intersect returns the intersection of b and other.
func (b *Bitmap) Intersect(other *Bitmap) *Bitmap {
output := NewBitmap()
iiter, _ := b.Containers.Iterator(0)
jiter, _ := other.Containers.Iterator(0)
i, j := iiter.Next(), jiter.Next()
ki, ci := iiter.Value()
kj, cj := jiter.Value()
for i && j {
if ki < kj {
i = iiter.Next()
ki, ci = iiter.Value()
} else if ki > kj {
j = jiter.Next()
kj, cj = jiter.Value()
} else { // ki == kj
output.Containers.Put(ki, intersect(ci, cj))
i, j = iiter.Next(), jiter.Next()
ki, ci = iiter.Value()
kj, cj = jiter.Value()
}
}
return output
}
// Union returns the bitwise union of b and other.
func (b *Bitmap) Union(other *Bitmap) *Bitmap {
output := NewBitmap()
iiter, _ := b.Containers.Iterator(0)
jiter, _ := other.Containers.Iterator(0)
i, j := iiter.Next(), jiter.Next()
ki, ci := iiter.Value()
kj, cj := jiter.Value()
for i || j {
if i && (!j || ki < kj) {
output.Containers.Put(ki, ci.Clone())
i = iiter.Next()
ki, ci = iiter.Value()
} else if j && (!i || ki > kj) {
output.Containers.Put(kj, cj.Clone())
j = jiter.Next()
kj, cj = jiter.Value()
} else { // ki == kj
output.Containers.Put(ki, union(ci, cj))
i, j = iiter.Next(), jiter.Next()
ki, ci = iiter.Value()
kj, cj = jiter.Value()
}
}
return output
}
// Difference returns the difference of b and other.
func (b *Bitmap) Difference(other *Bitmap) *Bitmap {
output := NewBitmap()
iiter, _ := b.Containers.Iterator(0)
jiter, _ := other.Containers.Iterator(0)
i, j := iiter.Next(), jiter.Next()
ki, ci := iiter.Value()
kj, cj := jiter.Value()
for i || j {
if i && (!j || ki < kj) {
output.Containers.Put(ki, ci.Clone())
i = iiter.Next()
ki, ci = iiter.Value()
} else if j && (!i || ki > kj) {
j = jiter.Next()
kj, cj = jiter.Value()
} else { // ki == kj
output.Containers.Put(ki, difference(ci, cj))
i, j = iiter.Next(), jiter.Next()
ki, ci = iiter.Value()
kj, cj = jiter.Value()
}
}
return output
}
// Xor returns the bitwise exclusive or of b and other.
func (b *Bitmap) Xor(other *Bitmap) *Bitmap {
output := NewBitmap()
iiter, _ := b.Containers.Iterator(0)
jiter, _ := other.Containers.Iterator(0)
i, j := iiter.Next(), jiter.Next()
ki, ci := iiter.Value()
kj, cj := jiter.Value()
for i || j {
if i && (!j || ki < kj) {
output.Containers.Put(ki, ci.Clone())
i = iiter.Next()
ki, ci = iiter.Value()
} else if j && (!i || ki > kj) {
output.Containers.Put(kj, cj.Clone())
j = jiter.Next()
kj, cj = jiter.Value()
} else { // ki == kj
output.Containers.Put(ki, xor(ci, cj))
i, j = iiter.Next(), jiter.Next()
ki, ci = iiter.Value()
kj, cj = jiter.Value()
}
}
return output
}
// removeEmptyContainers deletes all containers that have a count of zero.
func (b *Bitmap) removeEmptyContainers() {
citer, _ := b.Containers.Iterator(0)
for citer.Next() {
k, c := citer.Value()
if c.n == 0 {
b.Containers.Remove(k)
}
}
}
func (b *Bitmap) countEmptyContainers() int {
result := 0
citer, _ := b.Containers.Iterator(0)
for citer.Next() {
_, c := citer.Value()
if c.n == 0 {
result++
}
}
return result
}
// Optimize converts array and bitmap containers to run containers as necessary.
func (b *Bitmap) Optimize() {
citer, _ := b.Containers.Iterator(0)
for citer.Next() {
_, c := citer.Value()
c.optimize()
}
}
type errWriter struct {
w io.Writer
err error
n int
}
func (ew *errWriter) WriteUint16(b []byte, v uint16) {
if ew.err != nil {
return
}
var n int
binary.LittleEndian.PutUint16(b, v)
n, ew.err = ew.w.Write(b)
ew.n += n
}
func (ew *errWriter) WriteUint32(b []byte, v uint32) {
if ew.err != nil {
return
}
var n int
binary.LittleEndian.PutUint32(b, v)
n, ew.err = ew.w.Write(b)
ew.n += n
}
func (ew *errWriter) WriteUint64(b []byte, v uint64) {
if ew.err != nil {
return
}
var n int
binary.LittleEndian.PutUint64(b, v)
n, ew.err = ew.w.Write(b)
ew.n += n
}
// WriteTo writes b to w.
func (b *Bitmap) WriteTo(w io.Writer) (n int64, err error) {
b.Optimize()
// Remove empty containers before persisting.
//b.removeEmptyContainers()
containerCount := b.Containers.Size() - b.countEmptyContainers()
headerSize := headerBaseSize
byte2 := make([]byte, 2)
byte4 := make([]byte, 4)
byte8 := make([]byte, 8)
// Build header before writing individual container blocks.
// Metadata for each container is 8+2+2+4 = sizeof(key) + sizeof(containerType)+sizeof(cardinality) + sizeof(file offset)
// Cookie header section.
ew := &errWriter{
w: w,
n: 0,
}
ew.WriteUint32(byte4, cookie)
ew.WriteUint32(byte4, uint32(containerCount))
// Descriptive header section: encode keys and cardinality.
// Key and cardinality are stored interleaved here, 12 bytes per container.
citer, _ := b.Containers.Iterator(0)
for citer.Next() {
key, c := citer.Value()
// Verify container count before writing.
// TODO: instead of commenting this out, we need to make it a configuration option
//count := c.count()
//assert(c.count() == c.n, "cannot write container count, mismatch: count=%d, n=%d", count, c.n)
if c.n > 0 {
ew.WriteUint64(byte8, key)
ew.WriteUint16(byte2, uint16(c.containerType))
ew.WriteUint16(byte2, uint16(c.n-1))
}
}
// Offset header section: write the offset for each container block.
// 4 bytes per container.
offset := uint32(headerSize + (containerCount * (8 + 2 + 2 + 4)))
citer, _ = b.Containers.Iterator(0)
for citer.Next() {
_, c := citer.Value()
if c.n > 0 {
ew.WriteUint32(byte4, offset)
offset += uint32(c.size())
}
}
if ew.err != nil {
return int64(ew.n), ew.err
}
n = int64(headerSize + (containerCount * (8 + 2 + 2 + 4)))
// Container storage section: write each container block.
citer, _ = b.Containers.Iterator(0)
for citer.Next() {
_, c := citer.Value()
if c.n > 0 {
nn, err := c.WriteTo(w)
n += nn
if err != nil {
return n, err
}
}
}
return n, nil
}
// unmarshalPilosaRoaring treats data as being encoded in Pilosa's 64 bit
// roaring format and decodes it into b.
func (b *Bitmap) unmarshalPilosaRoaring(data []byte) error {
if len(data) < headerBaseSize {
return errors.New("data too small")
}
// Verify the first two bytes are a valid magicNumber, and second two bytes match current storageVersion.
fileMagic := uint32(binary.LittleEndian.Uint16(data[0:2]))
fileVersion := uint32(binary.LittleEndian.Uint16(data[2:4]))
if fileMagic != magicNumber {
return fmt.Errorf("invalid roaring file, magic number %v is incorrect", fileMagic)
}
if fileVersion != storageVersion {
return fmt.Errorf("wrong roaring version, file is v%d, server requires v%d", fileVersion, storageVersion)
}
// Read key count in bytes sizeof(cookie):(sizeof(cookie)+sizeof(uint32)).
keyN := binary.LittleEndian.Uint32(data[4:8])
headerSize := headerBaseSize
b.Containers.Reset()
// Descriptive header section: Read container keys and cardinalities.
for i, buf := 0, data[headerSize:]; i < int(keyN); i, buf = i+1, buf[12:] {
b.Containers.PutContainerValues(
binary.LittleEndian.Uint64(buf[0:8]),
byte(binary.LittleEndian.Uint16(buf[8:10])),
int(binary.LittleEndian.Uint16(buf[10:12]))+1,
true)
}
opsOffset := headerSize + int(keyN)*12
// Read container offsets and attach data.
citer, _ := b.Containers.Iterator(0)
for i, buf := 0, data[opsOffset:]; i < int(keyN); i, buf = i+1, buf[4:] {
offset := binary.LittleEndian.Uint32(buf[0:4])
// Verify the offset is within the bounds of the input data.
if int(offset) >= len(data) {
return fmt.Errorf("offset out of bounds: off=%d, len=%d", offset, len(data))
}
// Map byte slice directly to the container data.
citer.Next()
_, c := citer.Value()
switch c.containerType {
case containerRun:
c.array = nil
c.bitmap = nil
runCount := binary.LittleEndian.Uint16(data[offset : offset+runCountHeaderSize])
c.runs = (*[0xFFFFFFF]interval16)(unsafe.Pointer(&data[offset+runCountHeaderSize]))[:runCount]
opsOffset = int(offset) + runCountHeaderSize + len(c.runs)*interval16Size
case containerArray:
c.runs = nil
c.bitmap = nil
c.array = (*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[offset]))[:c.n]
opsOffset = int(offset) + len(c.array)*2 // sizeof(uint32)
case containerBitmap:
c.array = nil
c.runs = nil
c.bitmap = (*[0xFFFFFFF]uint64)(unsafe.Pointer(&data[offset]))[:bitmapN]
opsOffset = int(offset) + len(c.bitmap)*8 // sizeof(uint64)
}
}
// Read ops log until the end of the file.
buf := data[opsOffset:]
for {
// Exit when there are no more ops to parse.
if len(buf) == 0 {
break
}
// Unmarshal the op and apply it.
var opr op
if err := opr.UnmarshalBinary(buf); err != nil {
// FIXME(benbjohnson): return error with position so file can be trimmed.
return err
}
opr.apply(b)
// Increase the op count.
b.opN++
// Move the buffer forward.
buf = buf[opr.size():]
}
return nil
}
// writeOp writes op to the OpWriter, if available.
func (b *Bitmap) writeOp(op *op) error {
if b.OpWriter == nil {
return nil
}
if _, err := op.WriteTo(b.OpWriter); err != nil {
return err
}
b.opN++
return nil
}
// Iterator returns a new iterator for the bitmap.
func (b *Bitmap) Iterator() *Iterator {
itr := &Iterator{bitmap: b}
itr.Seek(0)
return itr
}
// Info returns stats for the bitmap.
func (b *Bitmap) Info() bitmapInfo {
info := bitmapInfo{
OpN: b.opN,
Containers: make([]containerInfo, 0, b.Containers.Size()),
}
citer, _ := b.Containers.Iterator(0)
for citer.Next() {
k, c := citer.Value()
ci := c.info()
ci.Key = k
info.Containers = append(info.Containers, ci)
}
return info
}
// Check performs a consistency check on the bitmap. Returns nil if consistent.
func (b *Bitmap) Check() error {
var a ErrorList
// Check each container.
citer, _ := b.Containers.Iterator(0)
for citer.Next() {
k, c := citer.Value()
if err := c.check(); err != nil {
a.AppendWithPrefix(err, fmt.Sprintf("%d/", k))
}
}
if len(a) == 0 {
return nil
}
return a
}
// Flip performs a logical negate of the bits in the range [start,end].
func (b *Bitmap) Flip(start, end uint64) *Bitmap {
result := NewBitmap()
itr := b.Iterator()
v, eof := itr.Next()
//copy over previous bits.
for v < start && !eof {
result.DirectAdd(v)
v, eof = itr.Next()
}
//flip bits in range .
for i := start; i <= end; i++ {
if eof {
result.DirectAdd(i)
} else if v == i {
v, eof = itr.Next()
} else {
result.DirectAdd(i)
}
}
//add remaining.
for !eof {
result.DirectAdd(v)
v, eof = itr.Next()
}
return result
}
// bitmapInfo represents a point-in-time snapshot of bitmap stats.
type bitmapInfo struct {
OpN int
Containers []containerInfo
}
// Iterator represents an iterator over a Bitmap.
type Iterator struct {
bitmap *Bitmap
citer ContainerIterator
key uint64
c *Container
j, k int32 // i: container; j: array index, bit index, or run index; k: offset within the run
}
// Seek moves to the first value equal to or greater than `seek`.
func (itr *Iterator) Seek(seek uint64) {
// k should always be -1 unless we're seeking into a run container. Then the
// "if c.isRun" section will take care of it.
itr.k = -1
// Move to the correct container.
itr.citer, _ = itr.bitmap.Containers.Iterator(highbits(seek))
if !itr.citer.Next() {
itr.c = nil
return // eof
}
itr.key, itr.c = itr.citer.Value()
// Move to the correct value index inside the container.
lb := lowbits(seek)
if itr.c.isArray() {
// Find index in the container.
itr.j = search32(itr.c.array, lb)
if itr.j < 0 {
itr.j = -itr.j - 1
}
if itr.j < int32(len(itr.c.array)) {
itr.j--
return
}
// If it's at the end of the container then move to the next one.
if !itr.citer.Next() {
itr.c = nil
return
}
itr.key, itr.c = itr.citer.Value()
itr.j = -1
return
}
if itr.c.isRun() {
if seek == 0 {
itr.j, itr.k = 0, -1
}
j, contains := binSearchRuns(lb, itr.c.runs)
if contains {
itr.j = j
itr.k = int32(lb) - int32(itr.c.runs[j].start) - 1
} else {
// Set iterator to next value in the Bitmap.
itr.j = j
itr.k = -1
}
return
}
// If it's a bitmap container then move to index before the value and call next().
itr.j = int32(lb) - 1
}
// Next returns the next value in the bitmap.
// Returns eof as true if there are no values left in the iterator.
func (itr *Iterator) Next() (v uint64, eof bool) {
if itr.c == nil {
return 0, true
}
// Iterate over containers until we find the next value or EOF.
for {
if itr.c.isArray() {
if itr.j >= itr.c.n-1 {
// Reached end of array, move to the next container.
if !itr.citer.Next() {
itr.c = nil
return 0, true
}
itr.key, itr.c = itr.citer.Value()
itr.j = -1
continue
}
itr.j++
return itr.peek(), false
}
if itr.c.isRun() {
// Because itr.j for an array container defaults to -1
// but defaults to 0 for a run container, we need to
// standardize on treating -1 as our default value for itr.j.
// Note that this is easier than changing the default to 0
// because the array logic uses the negative number space
// to represent offsets to an array position that isn't filled
// (-1 being the first empty space in an array, or 0).
if itr.j == -1 {
itr.j++
}
// If the container is empty, move to the next container.
if len(itr.c.runs) == 0 {
if !itr.citer.Next() {
itr.c = nil
return 0, true
}
itr.key, itr.c = itr.citer.Value()
itr.j = -1
continue
}
r := itr.c.runs[itr.j]
runLength := int32(r.last - r.start)
if itr.k >= runLength {
// Reached end of run, move to the next run.
itr.j, itr.k = itr.j+1, -1
}
if itr.j >= int32(len(itr.c.runs)) {
// Reached end of runs, move to the next container.
if !itr.citer.Next() {
itr.c = nil
return 0, true
}
itr.key, itr.c = itr.citer.Value()
itr.j = -1
continue
}
itr.k++
return itr.peek(), false
}
// Move to the next possible index in the bitmap container.
itr.j++
// Find first non-zero bit in current bitmap, if possible.
hb := itr.j >> 6
if hb >= int32(len(itr.c.bitmap)) {
if !itr.citer.Next() {
itr.c = nil
return 0, true
}
itr.key, itr.c = itr.citer.Value()
itr.j = -1
continue
}
lb := itr.c.bitmap[hb] >> (uint(itr.j) % 64)
if lb != 0 {
itr.j = itr.j + int32(trailingZeroN(lb))
return itr.peek(), false
}
// Otherwise iterate through remaining bitmaps to find next bit.
for hb++; hb < int32(len(itr.c.bitmap)); hb++ {
if itr.c.bitmap[hb] != 0 {
itr.j = hb<<6 + int32(trailingZeroN(itr.c.bitmap[hb]))
return itr.peek(), false
}
}
// If no bits found then move to the next container.
if !itr.citer.Next() {
itr.c = nil
return 0, true
}
itr.key, itr.c = itr.citer.Value()
itr.j = -1
}
}
// peek returns the current value.
func (itr *Iterator) peek() uint64 {
if itr.c == nil {
return 0
}
if itr.c.isArray() {
return itr.key<<16 | uint64(itr.c.array[itr.j])
}
if itr.c.isRun() {
return itr.key<<16 | uint64(itr.c.runs[itr.j].start+uint16(itr.k))
}
return itr.key<<16 | uint64(itr.j)
}
// ArrayMaxSize represents the maximum size of array containers.
const ArrayMaxSize = 4096
// runMaxSize represents the maximum size of run length encoded containers.
const runMaxSize = 2048
// Container represents a Container for uint16 integers.
//
// These are used for storing the low bits of numbers in larger sets of uint64.
// The high bits are stored in a Container's key which is tracked by a separate
// data structure. Integers in a Container can be encoded in one of three ways -
// the encoding used is usually whichever is most compact, though any Container
// type should be able to encode any set of integers safely. For containers with
// less than 4,096 values, an array is often used. Containers with long runs of
// integers would use run length encoding, and more random data usually uses
// bitmap encoding.
type Container struct {
mapped bool // mapped directly to a byte slice when true
containerType byte // array, bitmap, or run
n int32 // number of integers in container
array []uint16 // used for array containers
bitmap []uint64 // used for bitmap containers
runs []interval16 // used for RLE containers
}
type interval16 struct {
start uint16
last uint16
}
// runlen returns the count of integers in the interval.
func (iv interval16) runlen() int32 {
return 1 + int32(iv.last-iv.start)
}
// newContainer returns a new instance of container.
func NewContainer() *Container {
statsHit("NewContainer")
return &Container{containerType: containerArray}
}
// Mapped returns true if the container is mapped directly to a byte slice
func (c *Container) Mapped() bool {
return c.mapped
}
// N returns the cached bit count of the container
func (c *Container) N() int32 {
return c.n
}
// Update updates the container
func (c *Container) Update(containerType byte, n int32, mapped bool) {
c.containerType = containerType
c.n = n
c.mapped = mapped
}
// isArray returns true if the container is an array container.
func (c *Container) isArray() bool {
return c.containerType == containerArray
}
// isBitmap returns true if the container is a bitmap container.
func (c *Container) isBitmap() bool {
return c.containerType == containerBitmap
}
// isRun returns true if the container is a run-length-encoded container.
func (c *Container) isRun() bool {
return c.containerType == containerRun
}
// unmap creates copies of the containers data in the heap.
//
// This is performed when altering the container since its contents could be
// pointing at a read-only mmap.
func (c *Container) unmap() {
if !c.mapped {
return
}
switch c.containerType {
case containerArray:
tmp := make([]uint16, len(c.array))
copy(tmp, c.array)
c.array = tmp
case containerBitmap:
tmp := make([]uint64, len(c.bitmap))
copy(tmp, c.bitmap)
c.bitmap = tmp
case containerRun:
tmp := make([]interval16, len(c.runs))
copy(tmp, c.runs)
c.runs = tmp
}
c.mapped = false
}
// count counts all bits in the container.
func (c *Container) count() (n int32) {
return c.countRange(0, maxContainerVal+1)
}
// countRange counts the number of bits set between [start, end).
func (c *Container) countRange(start, end int32) (n int32) {
if c.isArray() {
return c.arrayCountRange(start, end)
} else if c.isRun() {
return c.runCountRange(start, end)
}
return c.bitmapCountRange(start, end)
}
func (c *Container) arrayCountRange(start, end int32) (n int32) {
i := int32(sort.Search(len(c.array), func(i int) bool { return int32(c.array[i]) >= start }))
for ; i < int32(len(c.array)); i++ {
v := int32(c.array[i])
if v >= end {
break
}
n++
}
return n
}
func (c *Container) bitmapCountRange(start, end int32) int32 {
var n uint64
i, j := start/64, end/64
// Special case when start and end fall in the same word.
if i == j {
offi, offj := uint(start%64), uint(64-end%64)
n += popcount((c.bitmap[i] >> offi) << (offj + offi))
return int32(n)
}
// Count partial starting word.
if off := uint(start) % 64; off != 0 {
n += popcount(c.bitmap[i] >> off)
i++
}
// Count words in between.
for ; i < j; i++ {
n += popcount(c.bitmap[i])
}
// Count partial ending word.
if j < int32(len(c.bitmap)) {
off := 64 - (uint(end) % 64)
n += popcount(c.bitmap[j] << off)
}
return int32(n)
}
func (c *Container) runCountRange(start, end int32) (n int32) {
for _, iv := range c.runs {
// iv is before range
if int32(iv.last) < start {
continue
}
// iv is after range
if end < int32(iv.start) {
break
}
// iv is superset of range
if int32(iv.start) < start && int32(iv.last) > end {
return end - start
}
// iv is subset of range
if int32(iv.start) >= start && int32(iv.last) < end {
n += iv.runlen()
}
// iv overlaps beginning of range
if int32(iv.start) < start && int32(iv.last) < end {
n += int32(iv.last) - start + 1
}
// iv overlaps end of range
if int32(iv.start) > start && int32(iv.last) >= end {
n += end - int32(iv.start)
}
}
return n
}
// add adds a value to the container.
func (c *Container) add(v uint16) (added bool) {
if c.isArray() {
added = c.arrayAdd(v)
} else if c.isRun() {
added = c.runAdd(v)
} else {
added = c.bitmapAdd(v)
}
if added {
c.n++
}
return added
}
func (c *Container) arrayAdd(v uint16) bool {
// Optimize appending to the end of an array container.
if c.n > 0 && c.n < ArrayMaxSize && c.isArray() && c.array[c.n-1] < v {
statsHit("arrayAdd/append")
c.unmap()
c.array = append(c.array, v)
return true
}
// Find index of the integer in the container. Exit if it already exists.
i := search32(c.array, v)
if i >= 0 {
return false
}
// Convert to a bitmap container if too many values are in an array container.
if c.n >= ArrayMaxSize {
statsHit("arrayAdd/arrayToBitmap")
c.arrayToBitmap()
return c.bitmapAdd(v)
}
// Otherwise insert into array.
statsHit("arrayAdd/insert")
c.unmap()
i = -i - 1
c.array = append(c.array, 0)
copy(c.array[i+1:], c.array[i:])
c.array[i] = v
return true
}
func (c *Container) bitmapAdd(v uint16) bool {
if c.bitmapContains(v) {
return false
}
c.unmap()
c.bitmap[v/64] |= (1 << uint64(v%64))
return true
}
func (c *Container) runAdd(v uint16) bool {
if len(c.runs) == 0 {
c.unmap()
c.runs = []interval16{{start: v, last: v}}
return true
}
i := sort.Search(len(c.runs),
func(i int) bool { return c.runs[i].last >= v })
if i == len(c.runs) {
i--
}
iv := c.runs[i]
if v >= iv.start && iv.last >= v {
return false
}
c.unmap()
if iv.last < v {
if iv.last == v-1 {
c.runs[i].last++
} else {
c.runs = append(c.runs, interval16{start: v, last: v})
}
} else if v+1 == iv.start {
// combining two intervals
if i > 0 && c.runs[i-1].last == v-1 {
c.runs[i-1].last = iv.last
c.runs = append(c.runs[:i], c.runs[i+1:]...)
return true
}
// just before an interval
c.runs[i].start--
} else if i > 0 && v-1 == c.runs[i-1].last {
// just after an interval
c.runs[i-1].last++
} else {
// alone
newIv := interval16{start: v, last: v}
c.runs = append(c.runs[:i], append([]interval16{newIv}, c.runs[i:]...)...)
}
return true
}
// Contains returns true if v is in the container.
func (c *Container) Contains(v uint16) bool {
if c.isArray() {
return c.arrayContains(v)
} else if c.isRun() {
return c.runContains(v)
} else {
return c.bitmapContains(v)
}
}
func (c *Container) bitmapCountRuns() (r int32) {
for i := 0; i < 1023; i++ {
v, v1 := c.bitmap[i], c.bitmap[i+1]
r = r + int32(popcount((v<<1)&^v)+((v>>63)&^v1))
}
vl := c.bitmap[len(c.bitmap)-1]
r = r + int32(popcount((vl<<1)&^vl)+vl>>63)
return r
}
func (c *Container) arrayCountRuns() (r int32) {
prev := int32(-2)
for _, v := range c.array {
if prev+1 != int32(v) {
r++
}
prev = int32(v)
}
return r
}
func (c *Container) countRuns() (r int32) {
if c.isArray() {
return c.arrayCountRuns()
} else if c.isBitmap() {
return c.bitmapCountRuns()
} else if c.isRun() {
return int32(len(c.runs))
}
// sure hope this never happens
return 0
}
// optimize converts the container to the type which will take up the least
// amount of space.
func (c *Container) optimize() {
if c.n == 0 {
statsHit("optimize/empty")
return
}
runs := c.countRuns()
var newType byte
if runs <= runMaxSize && runs <= c.n/2 {
newType = containerRun
} else if c.n < ArrayMaxSize {
newType = containerArray
} else {
newType = containerBitmap
}
// Then convert accordingly.
if c.isArray() {
if newType == containerBitmap {
statsHit("optimize/arrayToBitmap")
c.arrayToBitmap()
} else if newType == containerRun {
statsHit("optimize/arrayToRun")
c.arrayToRun()
} else {
statsHit("optimize/arrayUnchanged")
}
} else if c.isBitmap() {
if newType == containerArray {
statsHit("optimize/bitmapToArray")
c.bitmapToArray()
} else if newType == containerRun {
statsHit("optimize/bitmapToRun")
c.bitmapToRun()
} else {
statsHit("optimize/bitmapUnchanged")
}
} else if c.isRun() {
if newType == containerBitmap {
statsHit("optimize/runToBitmap")
c.runToBitmap()
} else if newType == containerArray {
statsHit("optimize/runToArray")
c.runToArray()
} else {
statsHit("optimize/runUnchanged")
}
}
}
func (c *Container) arrayContains(v uint16) bool {
return search32(c.array, v) >= 0
}
func (c *Container) bitmapContains(v uint16) bool {
return (c.bitmap[v/64] & (1 << uint64(v%64))) != 0
}
// binSearchRuns returns the index of the run containing v, and true, when v is contained;
// or the index of the next run starting after v, and false, when v is not contained.
func binSearchRuns(v uint16, a []interval16) (int32, bool) {
i := int32(sort.Search(len(a),
func(i int) bool { return a[i].last >= v }))
if i < int32(len(a)) {
return i, (v >= a[i].start) && (v <= a[i].last)
}
return i, false
}
// runContains determines if v is in the container assuming c is a run
// container.
func (c *Container) runContains(v uint16) bool {
_, found := binSearchRuns(v, c.runs)
return found
}
// remove removes a value from the container.
func (c *Container) remove(v uint16) (removed bool) {
if c.isArray() {
removed = c.arrayRemove(v)
} else if c.isRun() {
removed = c.runRemove(v)
} else {
removed = c.bitmapRemove(v)
}
if removed {
c.n--
}
return removed
}
func (c *Container) arrayRemove(v uint16) bool {
i := search32(c.array, v)
if i < 0 {
return false
}
c.unmap()
c.array = append(c.array[:i], c.array[i+1:]...)
return true
}
func (c *Container) bitmapRemove(v uint16) bool {
if !c.bitmapContains(v) {
return false
}
c.unmap()
// Lower count and remove element.
// c.n-- // TODO removed this - test it
c.bitmap[v/64] &^= (uint64(1) << uint(v%64))
// Convert to array if we go below the threshold.
if c.n == ArrayMaxSize {
statsHit("bitmapRemove/bitmapToArray")
c.bitmapToArray()
}
return true
}
// runRemove removes v from a run container, and returns true if v was removed.
func (c *Container) runRemove(v uint16) bool {
i, contains := binSearchRuns(v, c.runs)
if !contains {
return false
}
c.unmap()
if v == c.runs[i].last && v == c.runs[i].start {
c.runs = append(c.runs[:i], c.runs[i+1:]...)
} else if v == c.runs[i].last {
c.runs[i].last--
} else if v == c.runs[i].start {
c.runs[i].start++
} else if v > c.runs[i].start {
last := c.runs[i].last
c.runs[i].last = v - 1
c.runs = append(c.runs[:i+1], append([]interval16{{start: v + 1, last: last}}, c.runs[i+1:]...)...)
}
return true
}
// max returns the maximum value in the container.
func (c *Container) max() uint16 {
if c.isArray() {
return c.arrayMax()
} else if c.isRun() {
return c.runMax()
} else {
return c.bitmapMax()
}
}
func (c *Container) arrayMax() uint16 {
if len(c.array) == 0 {
return 0 // probably hiding some ugly bug but it prevents a crash
}
return c.array[len(c.array)-1]
}
func (c *Container) bitmapMax() uint16 {
// Search bitmap in reverse order.
for i := len(c.bitmap); i > 0; i-- {
// If value is zero then skip.
v := c.bitmap[i-1]
if v != 0 {
r := bits.LeadingZeros64(v)
return uint16((i-1)*64 + 63 - r)
}
}
return 0
}
func (c *Container) runMax() uint16 {
if len(c.runs) == 0 {
return 0
}
return c.runs[len(c.runs)-1].last
}
// bitmapToArray converts from bitmap format to array format.
func (c *Container) bitmapToArray() {
statsHit("bitmapToArray")
c.array = make([]uint16, 0, c.n)
c.containerType = containerArray
// return early if empty
if c.n == 0 {
c.bitmap = nil
c.mapped = false
return
}
for i, bitmap := range c.bitmap {
for bitmap != 0 {
t := bitmap & -bitmap
c.array = append(c.array, uint16((i*64 + int(popcount(t-1)))))
bitmap ^= t
}
}
c.bitmap = nil
c.mapped = false
}
// arrayToBitmap converts from array format to bitmap format.
func (c *Container) arrayToBitmap() {
statsHit("arrayToBitmap")
c.bitmap = make([]uint64, bitmapN)
c.containerType = containerBitmap
// return early if empty
if c.n == 0 {
c.array = nil
c.mapped = false
return
}
for _, v := range c.array {
c.bitmap[int(v)/64] |= (uint64(1) << uint(v%64))
}
c.array = nil
c.mapped = false
}
// runToBitmap converts from RLE format to bitmap format.
func (c *Container) runToBitmap() {
statsHit("runToBitmap")
c.bitmap = make([]uint64, bitmapN)
c.containerType = containerBitmap
// return early if empty
if c.n == 0 {
c.runs = nil
c.mapped = false
return
}
for _, r := range c.runs {
// TODO this can be ~64x faster for long runs by setting maxBitmap instead of single bits
//note v must be int or will overflow
for v := int(r.start); v <= int(r.last); v++ {
c.bitmap[v/64] |= (uint64(1) << uint(v%64))
}
}
c.runs = nil
c.mapped = false
}
// bitmapToRun converts from bitmap format to RLE format.
func (c *Container) bitmapToRun() {
statsHit("bitmapToRun")
c.containerType = containerRun
// return early if empty
if c.n == 0 {
c.runs = make([]interval16, 0)
c.bitmap = nil
c.mapped = false
return
}
numRuns := c.bitmapCountRuns()
c.runs = make([]interval16, 0, numRuns)
current := c.bitmap[0]
var i, start, last uint16
for {
// skip while empty
for current == 0 && i < bitmapN-1 {
i++
current = c.bitmap[i]
}
if current == 0 {
break
}
currentStart := uint16(trailingZeroN(current))
start = 64*i + currentStart
// pad LSBs with 1s
current = current | (current - 1)
// find next 0
for current == maxBitmap && i < bitmapN-1 {
i++
current = c.bitmap[i]
}
if current == maxBitmap {
// bitmap[1023] == maxBitmap
c.runs = append(c.runs, interval16{start, maxContainerVal})
break
}
currentLast := uint16(trailingZeroN(^current))
last = 64*i + currentLast
c.runs = append(c.runs, interval16{start, last - 1})
// pad LSBs with 0s
current = current & (current + 1)
}
c.bitmap = nil
c.mapped = false
}
// arrayToRun converts from array format to RLE format.
func (c *Container) arrayToRun() {
statsHit("arrayToRun")
c.containerType = containerRun
// return early if empty
if c.n == 0 {
c.runs = make([]interval16, 0)
c.array = nil
c.mapped = false
return
}
numRuns := c.arrayCountRuns()
c.runs = make([]interval16, 0, numRuns)
start := c.array[0]
for i, v := range c.array[1:] {
if v-c.array[i] > 1 {
// if current-previous > 1, one run ends and another begins
c.runs = append(c.runs, interval16{start, c.array[i]})
start = v
}
}
// append final run
c.runs = append(c.runs, interval16{start, c.array[c.n-1]})
c.array = nil
c.mapped = false
}
// runToArray converts from RLE format to array format.
func (c *Container) runToArray() {
statsHit("runToArray")
c.containerType = containerArray
c.array = make([]uint16, 0, c.n)
// return early if empty
if c.n == 0 {
c.runs = nil
c.mapped = false
return
}
for _, r := range c.runs {
for v := int(r.start); v <= int(r.last); v++ {
c.array = append(c.array, uint16(v))
}
}
c.runs = nil
c.mapped = false
}
// Clone returns a copy of c.
func (c *Container) Clone() *Container {
statsHit("Container/Clone")
other := &Container{n: c.n, containerType: c.containerType}
switch c.containerType {
case containerArray:
statsHit("Container/Clone/Array")
other.array = make([]uint16, len(c.array))
copy(other.array, c.array)
case containerBitmap:
statsHit("Container/Clone/Bitmap")
other.bitmap = make([]uint64, len(c.bitmap))
copy(other.bitmap, c.bitmap)
case containerRun:
statsHit("Container/Clone/Run")
other.runs = make([]interval16, len(c.runs))
copy(other.runs, c.runs)
}
return other
}
// WriteTo writes c to w.
func (c *Container) WriteTo(w io.Writer) (n int64, err error) {
if c.isArray() {
return c.arrayWriteTo(w)
} else if c.isRun() {
return c.runWriteTo(w)
} else {
return c.bitmapWriteTo(w)
}
}
func (c *Container) arrayWriteTo(w io.Writer) (n int64, err error) {
statsHit("Container/arrayWriteTo")
if len(c.array) == 0 {
return 0, nil
}
// Verify all elements are valid.
// TODO: instead of commenting this out, we need to make it a configuration option
// for _, v := range c.array {
// assert(lowbits(uint64(v)) == v, "cannot write array value out of range: %d", v)
//}
// Write sizeof(uint16) * cardinality bytes.
nn, err := w.Write((*[0xFFFFFFF]byte)(unsafe.Pointer(&c.array[0]))[:2*c.n])
return int64(nn), err
}
func (c *Container) bitmapWriteTo(w io.Writer) (n int64, err error) {
statsHit("Container/bitmapWriteTo")
// Write sizeof(uint64) * bitmapN bytes.
nn, err := w.Write((*[0xFFFFFFF]byte)(unsafe.Pointer(&c.bitmap[0]))[:(8 * bitmapN)])
return int64(nn), err
}
func (c *Container) runWriteTo(w io.Writer) (n int64, err error) {
statsHit("Container/runWriteTo")
if len(c.runs) == 0 {
return 0, nil
}
var byte2 [2]byte
binary.LittleEndian.PutUint16(byte2[:], uint16(len(c.runs)))
_, err = w.Write(byte2[:])
if err != nil {
return 0, err
}
nn, err := w.Write((*[0xFFFFFFF]byte)(unsafe.Pointer(&c.runs[0]))[:interval16Size*len(c.runs)])
return int64(runCountHeaderSize + nn), err
}
// size returns the encoded size of the container, in bytes.
func (c *Container) size() int {
if c.isArray() {
return len(c.array) * 2 // sizeof(uint16)
} else if c.isRun() {
return len(c.runs)*interval16Size + runCountHeaderSize
} else {
return len(c.bitmap) * 8 // sizeof(uint64)
}
}
// info returns the current stats about the container.
func (c *Container) info() containerInfo {
info := containerInfo{N: c.n}
if c.isArray() {
info.Type = "array"
info.Alloc = len(c.array) * 2 // sizeof(uint16)
} else if c.isRun() {
info.Type = "run"
info.Alloc = len(c.runs)*interval16Size + runCountHeaderSize
} else {
info.Type = "bitmap"
info.Alloc = len(c.bitmap) * 8 // sizeof(uint64)
}
if c.mapped {
if c.isArray() {
info.Pointer = unsafe.Pointer(&c.array[0])
} else if c.isRun() {
info.Pointer = unsafe.Pointer(&c.runs[0])
} else {
info.Pointer = unsafe.Pointer(&c.bitmap[0])
}
}
return info
}
// check performs a consistency check on the container.
func (c *Container) check() error {
var a ErrorList
if c.isArray() {
if int32(len(c.array)) != c.n {
a.Append(fmt.Errorf("array count mismatch: count=%d, n=%d", len(c.array), c.n))
}
} else if c.isRun() {
n := c.runCountRange(0, maxContainerVal+1)
if n != c.n {
a.Append(fmt.Errorf("run count mismatch: count=%d, n=%d", n, c.n))
}
} else if c.isBitmap() {
if n := c.bitmapCountRange(0, maxContainerVal+1); n != c.n {
a.Append(fmt.Errorf("bitmap count mismatch: count=%d, n=%d", n, c.n))
}
} else {
a.Append(fmt.Errorf("empty container"))
if c.n != 0 {
a.Append(fmt.Errorf("empty container with nonzero count: n=%d", c.n))
}
}
if a == nil {
return nil
}
return a
}
// containerInfo represents a point-in-time snapshot of container stats.
type containerInfo struct {
Key uint64 // container key
Type string // container type (array, bitmap, or run)
N int32 // number of bits
Alloc int // memory used
Pointer unsafe.Pointer // offset within the mmap
}
// flip returns a new container containing the inverse of all
// bits in a.
func flip(a *Container) *Container { // nolint: deadcode
if a.isArray() {
return flipArray(a)
} else if a.isRun() {
return flipRun(a)
} else {
return flipBitmap(a)
}
}
func flipArray(b *Container) *Container {
statsHit("flipArray")
// TODO: actually implement this
x := b.Clone()
x.arrayToBitmap()
return flipBitmap(x)
}
func flipBitmap(b *Container) *Container {
statsHit("flipBitmap")
other := &Container{bitmap: make([]uint64, bitmapN), containerType: containerBitmap}
for i, bitmap := range b.bitmap {
other.bitmap[i] = ^bitmap
}
other.n = other.count()
return other
}
func flipRun(b *Container) *Container {
statsHit("flipRun")
// TODO: actually implement this
x := b.Clone()
x.runToBitmap()
return flipBitmap(x)
}
func intersectionCount(a, b *Container) int32 {
if a.isArray() {
if b.isArray() {
return intersectionCountArrayArray(a, b)
} else if b.isRun() {
return intersectionCountArrayRun(a, b)
} else {
return intersectionCountArrayBitmap(a, b)
}
} else if a.isRun() {
if b.isArray() {
return intersectionCountArrayRun(b, a)
} else if b.isRun() {
return intersectionCountRunRun(a, b)
} else {
return intersectionCountBitmapRun(b, a)
}
} else {
if b.isArray() {
return intersectionCountArrayBitmap(b, a)
} else if b.isRun() {
return intersectionCountBitmapRun(a, b)
} else {
return intersectionCountBitmapBitmap(a, b)
}
}
}
func intersectionCountArrayArray(a, b *Container) (n int32) {
statsHit("intersectionCount/ArrayArray")
s1, s2 := a.array, b.array
if len(s1) == 0 || len(s2) == 0 {
return 0
}
if len(s1) > len(s2) {
s1, s2 = s2, s1
}
l2 := len(s2)
i2 := 0
v2 := s2[0]
for _, v1 := range s1 {
for v2 < v1 {
i2++
if i2 >= l2 {
return n
}
v2 = s2[i2]
}
if v2 == v1 {
n++
}
}
return n
}
func intersectionCountArrayRun(a, b *Container) (n int32) {
statsHit("intersectionCount/ArrayRun")
na, nb := len(a.array), len(b.runs)
for i, j := 0, 0; i < na && j < nb; {
va, vb := a.array[i], b.runs[j]
if va < vb.start {
i++
} else if va >= vb.start && va <= vb.last {
i++
n++
} else if va > vb.last {
j++
}
}
return n
}
func intersectionCountRunRun(a, b *Container) (n int32) {
statsHit("intersectionCount/RunRun")
na, nb := len(a.runs), len(b.runs)
for i, j := 0, 0; i < na && j < nb; {
va, vb := a.runs[i], b.runs[j]
if va.last < vb.start {
// |--va--| |--vb--|
i++
} else if va.start > vb.last {
// |--vb--| |--va--|
j++
} else if va.last > vb.last && va.start >= vb.start {
// |--vb-|-|-va--|
n += 1 + int32(vb.last-va.start)
j++
} else if va.last > vb.last && va.start < vb.start {
// |--va|--vb--|--|
n += 1 + int32(vb.last-vb.start)
j++
} else if va.last <= vb.last && va.start >= vb.start {
// |--vb|--va--|--|
n += 1 + int32(va.last-va.start)
i++
} else if va.last <= vb.last && va.start < vb.start {
// |--va-|-|-vb--|
n += 1 + int32(va.last-vb.start)
i++
}
}
return n
}
func intersectionCountBitmapRun(a, b *Container) (n int32) {
statsHit("intersectionCount/BitmapRun")
for _, iv := range b.runs {
n += a.bitmapCountRange(int32(iv.start), int32(iv.last)+1)
}
return n
}
func intersectionCountArrayBitmap(a, b *Container) (n int32) {
statsHit("intersectionCount/ArrayBitmap")
ln := len(b.bitmap)
for _, val := range a.array {
i := int(val >> 6)
if i >= ln {
break
}
off := val % 64
n += int32(b.bitmap[i]>>off) & 1
}
return n
}
func intersectionCountBitmapBitmap(a, b *Container) (n int32) {
statsHit("intersectionCount/BitmapBitmap")
return int32(popcountAndSlice(a.bitmap, b.bitmap))
}
func intersect(a, b *Container) *Container {
if a.isArray() {
if b.isArray() {
return intersectArrayArray(a, b)
} else if b.isRun() {
return intersectArrayRun(a, b)
} else {
return intersectArrayBitmap(a, b)
}
} else if a.isRun() {
if b.isArray() {
return intersectArrayRun(b, a)
} else if b.isRun() {
return intersectRunRun(a, b)
} else {
return intersectBitmapRun(b, a)
}
} else {
if b.isArray() {
return intersectArrayBitmap(b, a)
} else if b.isRun() {
return intersectBitmapRun(a, b)
} else {
return intersectBitmapBitmap(a, b)
}
}
}
func intersectArrayArray(a, b *Container) *Container {
statsHit("intersect/ArrayArray")
output := &Container{containerType: containerArray}
na, nb := len(a.array), len(b.array)
for i, j := 0, 0; i < na && j < nb; {
va, vb := a.array[i], b.array[j]
if va < vb {
i++
} else if va > vb {
j++
} else {
output.array = append(output.array, va)
i, j = i+1, j+1
}
}
output.n = int32(len(output.array))
return output
}
// intersectArrayRun computes the intersect of an array container and a run
// container. The return is always an array container (since it's guaranteed to
// be low-cardinality)
func intersectArrayRun(a, b *Container) *Container {
statsHit("intersect/ArrayRun")
output := &Container{containerType: containerArray}
na, nb := len(a.array), len(b.runs)
for i, j := 0, 0; i < na && j < nb; {
va, vb := a.array[i], b.runs[j]
if va < vb.start {
i++
} else if va > vb.last {
j++
} else {
output.array = append(output.array, va)
i++
}
}
output.n = int32(len(output.array))
return output
}
// intersectRunRun computes the intersect of two run containers.
func intersectRunRun(a, b *Container) *Container {
statsHit("intersect/RunRun")
output := &Container{containerType: containerRun}
na, nb := len(a.runs), len(b.runs)
for i, j := 0, 0; i < na && j < nb; {
va, vb := a.runs[i], b.runs[j]
if va.last < vb.start {
// |--va--| |--vb--|
i++
} else if vb.last < va.start {
// |--vb--| |--va--|
j++
} else if va.last > vb.last && va.start >= vb.start {
// |--vb-|-|-va--|
output.n += output.runAppendInterval(interval16{start: va.start, last: vb.last})
j++
} else if va.last > vb.last && va.start < vb.start {
// |--va|--vb--|--|
output.n += output.runAppendInterval(vb)
j++
} else if va.last <= vb.last && va.start >= vb.start {
// |--vb|--va--|--|
output.n += output.runAppendInterval(va)
i++
} else if va.last <= vb.last && va.start < vb.start {
// |--va-|-|-vb--|
output.n += output.runAppendInterval(interval16{start: vb.start, last: va.last})
i++
}
}
if output.n < ArrayMaxSize && int32(len(output.runs)) > output.n/2 {
output.runToArray()
} else if len(output.runs) > runMaxSize {
output.runToBitmap()
}
return output
}
// intersectBitmapRun returns an array container if either container's
// cardinality is <= ArrayMaxSize. Otherwise it returns a bitmap container.
func intersectBitmapRun(a, b *Container) *Container {
statsHit("intersect/BitmapRun")
var output *Container
if b.n <= ArrayMaxSize || a.n <= ArrayMaxSize {
// output is array container
output = &Container{containerType: containerArray}
for _, iv := range b.runs {
for i := iv.start; i <= iv.last; i++ {
if a.bitmapContains(i) {
output.array = append(output.array, i)
}
// If the run ends the container, break to avoid an infinite loop.
if i == 65535 {
break
}
}
}
output.n = int32(len(output.array))
} else {
// right now this iterates through the runs and sets integers in the
// bitmap that are in the runs. alternately, we could zero out ranges in
// the bitmap which are between runs.
output = &Container{
bitmap: make([]uint64, bitmapN),
containerType: containerBitmap,
}
for j := 0; j < len(b.runs); j++ {
vb := b.runs[j]
i := vb.start >> 6 // index into a
vastart := i << 6
valast := vastart + 63
for valast >= vb.start && vastart <= vb.last && i < bitmapN {
if vastart >= vb.start && valast <= vb.last { // a within b
output.bitmap[i] = a.bitmap[i]
output.n += int32(popcount(a.bitmap[i]))
} else if vb.start >= vastart && vb.last <= valast { // b within a
var mask uint64 = ((1 << (vb.last - vb.start + 1)) - 1) << (vb.start - vastart)
bits := a.bitmap[i] & mask
output.bitmap[i] |= bits
output.n += int32(popcount(bits))
} else if vastart < vb.start { // a overlaps front of b
offset := 64 - (1 + valast - vb.start)
bits := (a.bitmap[i] >> offset) << offset
output.bitmap[i] |= bits
output.n += int32(popcount(bits))
} else if vb.start < vastart { // b overlaps front of a
offset := 64 - (1 + vb.last - vastart)
bits := (a.bitmap[i] << offset) >> offset
output.bitmap[i] |= bits
output.n += int32(popcount(bits))
}
// update loop vars
i++
vastart = i << 6
valast = vastart + 63
}
}
}
return output
}
func intersectArrayBitmap(a, b *Container) *Container {
statsHit("intersect/ArrayBitmap")
output := &Container{containerType: containerArray}
for _, va := range a.array {
bmidx := va / 64
bidx := va % 64
mask := uint64(1) << bidx
b := b.bitmap[bmidx]
if b&mask > 0 {
output.array = append(output.array, va)
}
}
output.n = int32(len(output.array))
return output
}
func intersectBitmapBitmap(a, b *Container) *Container {
statsHit("intersect/BitmapBitmap")
// 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]
buf = make([]uint64, bitmapN)
ob = buf[:bitmapN]
n int32
)
for i := 0; i < bitmapN; i++ {
ob[i] = ab[i] & bb[i]
n += int32(popcount(ob[i]))
}
output := &Container{
bitmap: ob,
n: n,
containerType: containerBitmap,
}
return output
}
func union(a, b *Container) *Container {
if a.isArray() {
if b.isArray() {
return unionArrayArray(a, b)
} else if b.isRun() {
return unionArrayRun(a, b)
} else {
return unionArrayBitmap(a, b)
}
} else if a.isRun() {
if b.isArray() {
return unionArrayRun(b, a)
} else if b.isRun() {
return unionRunRun(a, b)
} else {
return unionBitmapRun(b, a)
}
} else {
if b.isArray() {
return unionArrayBitmap(b, a)
} else if b.isRun() {
return unionBitmapRun(a, b)
} else {
return unionBitmapBitmap(a, b)
}
}
}
func unionArrayArray(a, b *Container) *Container {
statsHit("union/ArrayArray")
output := &Container{containerType: containerArray}
na, nb := len(a.array), len(b.array)
for i, j := 0, 0; ; {
if i >= na && j >= nb {
break
} else if i < na && j >= nb {
output.add(a.array[i])
i++
continue
} else if i >= na && j < nb {
output.add(b.array[j])
j++
continue
}
va, vb := a.array[i], b.array[j]
if va < vb {
output.add(va)
i++
} else if va > vb {
output.add(vb)
j++
} else {
output.add(va)
i, j = i+1, j+1
}
}
return output
}
// unionArrayRun optimistically assumes that the result will be a run container,
// and converts to a bitmap or array container afterwards if necessary.
func unionArrayRun(a, b *Container) *Container {
statsHit("union/ArrayRun")
if b.n == maxContainerVal+1 {
return b.Clone()
}
output := &Container{containerType: containerRun}
na, nb := len(a.array), len(b.runs)
var vb interval16
var va uint16
for i, j := 0, 0; i < na || j < nb; {
if i < na {
va = a.array[i]
}
if j < nb {
vb = b.runs[j]
}
if i < na && (j >= nb || va < vb.start) {
output.n += output.runAppendInterval(interval16{start: va, last: va})
i++
} else {
output.n += output.runAppendInterval(vb)
j++
}
}
if output.n < ArrayMaxSize {
output.runToArray()
} else if len(output.runs) > runMaxSize {
output.runToBitmap()
}
return output
}
// runAppendInterval adds the given interval to the run container. It assumes
// that the interval comes at the end of the list of runs, and does not check
// that this is the case. It will not behave correctly if the start of the given
// interval is earlier than the start of the last interval in the list of runs.
// Its return value is the amount by which the cardinality of the container was
// increased.
func (c *Container) runAppendInterval(v interval16) int32 {
if len(c.runs) == 0 {
c.runs = append(c.runs, v)
return int32(v.last-v.start) + 1
}
last := c.runs[len(c.runs)-1]
if last.last == maxContainerVal { //protect against overflow
return 0
}
if last.last+1 >= v.start && v.last > last.last {
c.runs[len(c.runs)-1].last = v.last
return int32(v.last - last.last)
} else if last.last+1 < v.start {
c.runs = append(c.runs, v)
return int32(v.last-v.start) + 1
}
return 0
}
func unionRunRun(a, b *Container) *Container {
statsHit("union/RunRun")
if a.n == maxContainerVal+1 {
return a.Clone()
}
if b.n == maxContainerVal+1 {
return b.Clone()
}
na, nb := len(a.runs), len(b.runs)
output := &Container{
runs: make([]interval16, 0, na+nb),
containerType: containerRun,
}
var va, vb interval16
for i, j := 0, 0; i < na || j < nb; {
if i < na {
va = a.runs[i]
}
if j < nb {
vb = b.runs[j]
}
if i < na && (j >= nb || va.start < vb.start) {
output.n += output.runAppendInterval(va)
i++
} else {
output.n += output.runAppendInterval(vb)
j++
}
}
if len(output.runs) > runMaxSize {
output.runToBitmap()
}
return output
}
func unionBitmapRun(a, b *Container) *Container {
statsHit("union/BitmapRun")
if b.n == maxContainerVal+1 {
return b.Clone()
}
if a.n == maxContainerVal+1 {
return a.Clone()
}
output := a.Clone()
for j := 0; j < len(b.runs); j++ {
output.bitmapSetRange(uint64(b.runs[j].start), uint64(b.runs[j].last)+1)
}
return output
}
const maxBitmap = 0xFFFFFFFFFFFFFFFF
// sets all bits in [i, j) (c must be a bitmap container)
func (c *Container) bitmapSetRange(i, j uint64) {
x := i >> 6
y := (j - 1) >> 6
var X uint64 = maxBitmap << (i % 64)
var Y uint64 = maxBitmap >> (63 - ((j - 1) % 64))
xcnt := popcount(X)
ycnt := popcount(Y)
if x == y {
c.n += int32((j - i) - popcount(c.bitmap[x]&(X&Y)))
c.bitmap[x] |= (X & Y)
} else {
c.n += int32(xcnt - popcount(c.bitmap[x]&X))
c.bitmap[x] |= X
for i := x + 1; i < y; i++ {
c.n += int32(64 - popcount(c.bitmap[i]))
c.bitmap[i] = maxBitmap
}
c.n += int32(ycnt - popcount(c.bitmap[y]&Y))
c.bitmap[y] |= Y
}
}
// xor's all bits in [i, j) with all true (c must be a bitmap container).
func (c *Container) bitmapXorRange(i, j uint64) {
x := i >> 6
y := (j - 1) >> 6
var X uint64 = maxBitmap << (i % 64)
var Y uint64 = maxBitmap >> (63 - ((j - 1) % 64))
if x == y {
cnt := popcount(c.bitmap[x])
c.bitmap[x] ^= (X & Y) //// flip
c.n += int32(popcount(c.bitmap[x]) - cnt)
} else {
cnt := popcount(c.bitmap[x])
c.bitmap[x] ^= X
c.n += int32(popcount(c.bitmap[x]) - cnt)
for i := x + 1; i < y; i++ {
cnt = popcount(c.bitmap[i])
c.bitmap[i] ^= maxBitmap
c.n += int32(popcount(c.bitmap[i]) - cnt)
}
cnt = popcount(c.bitmap[y])
c.bitmap[y] ^= Y
c.n += int32(popcount(c.bitmap[y]) - cnt)
}
}
// zeroes all bits in [i, j) (c must be a bitmap container)
func (c *Container) bitmapZeroRange(i, j uint64) {
x := i >> 6
y := (j - 1) >> 6
var X uint64 = maxBitmap << (i % 64)
var Y uint64 = maxBitmap >> (63 - ((j - 1) % 64))
if x == y {
c.n -= int32(popcount(c.bitmap[x] & (X & Y)))
c.bitmap[x] &= ^(X & Y)
} else {
c.n -= int32(popcount(c.bitmap[x] & X))
c.bitmap[x] &= ^X
for i := x + 1; i < y; i++ {
c.n -= int32(popcount(c.bitmap[i]))
c.bitmap[i] = 0
}
c.n -= int32(popcount(c.bitmap[y] & Y))
c.bitmap[y] &= ^Y
}
}
func (c *Container) equals(c2 *Container) bool {
if c.mapped != c2.mapped || c.containerType != c2.containerType || c.n != c2.n {
return false
}
if c.containerType == containerArray {
if len(c.array) != len(c2.array) {
return false
}
for i := 0; i < len(c.array); i++ {
if c.array[i] != c2.array[i] {
return false
}
}
} else if c.containerType == containerBitmap {
if len(c.bitmap) != len(c2.bitmap) {
return false
}
for i := 0; i < len(c.bitmap); i++ {
if c.bitmap[i] != c2.bitmap[i] {
return false
}
}
} else if c.containerType == containerRun {
if len(c.runs) != len(c2.runs) {
return false
}
for i := 0; i < len(c.runs); i++ {
if c.runs[i] != c2.runs[i] {
return false
}
}
} else {
panic(fmt.Sprintf("unknown container type: %v", c.containerType))
}
return true
}
func unionArrayBitmap(a, b *Container) *Container {
output := b.Clone()
for _, v := range a.array {
if !output.bitmapContains(v) {
output.bitmap[v/64] |= (1 << uint64(v%64))
output.n++
}
}
return output
}
func unionBitmapBitmap(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]
buf = make([]uint64, bitmapN)
ob = buf[:bitmapN]
n int32
)
for i := 0; i < bitmapN; i++ {
ob[i] = ab[i] | bb[i]
n += int32(popcount(ob[i]))
}
output := &Container{
bitmap: ob,
n: n,
containerType: containerBitmap,
}
return output
}
func difference(a, b *Container) *Container {
if a.isArray() {
if b.isArray() {
return differenceArrayArray(a, b)
} else if b.isRun() {
return differenceArrayRun(a, b)
} else {
return differenceArrayBitmap(a, b)
}
} else if a.isRun() {
if b.isArray() {
return differenceRunArray(a, b)
} else if b.isRun() {
return differenceRunRun(a, b)
} else {
return differenceRunBitmap(a, b)
}
} else {
if b.isArray() {
return differenceBitmapArray(a, b)
} else if b.isRun() {
return differenceBitmapRun(a, b)
} else {
return differenceBitmapBitmap(a, b)
}
}
}
// differenceArrayArray computes the difference bween two arrays.
func differenceArrayArray(a, b *Container) *Container {
statsHit("difference/ArrayArray")
output := &Container{containerType: containerArray}
na, nb := len(a.array), len(b.array)
for i, j := 0, 0; i < na; {
va := a.array[i]
if j >= nb {
output.add(va)
i++
continue
}
vb := b.array[j]
if va < vb {
output.add(va)
i++
} else if va > vb {
j++
} else {
i, j = i+1, j+1
}
}
return output
}
// differenceArrayRun computes the difference of an array from a run.
func differenceArrayRun(a, b *Container) *Container {
statsHit("difference/ArrayRun")
// func (ac *arrayContainer) iandNotRun16(rc *runContainer16) container {
if a.n == 0 || b.n == 0 {
return a.Clone()
}
output := &Container{array: make([]uint16, 0, a.n), containerType: containerArray}
// cardinality upper bound: card(A)
i := 0 // array index
j := 0 // run index
// handle overlap
for i < int(a.n) {
// keep all array elements before beginning of runs
if a.array[i] < b.runs[j].start {
output.add(a.array[i])
i++
continue
}
// if array element in run, skip it
if a.array[i] >= b.runs[j].start && a.array[i] <= b.runs[j].last {
i++
continue
}
// if array element larger than current run, check next run
if a.array[i] > b.runs[j].last {
j++
if j == len(b.runs) {
break
}
}
}
if i < len(a.array) {
// keep all array elements after end of runs
// It's possible that output was converted from array to bitmap in output.add()
// so check container type before proceeding.
if output.containerType == containerArray {
output.array = append(output.array, a.array[i:]...)
// TODO: consider handling container.n mutations in one place
// like we do with container.add().
output.n += int32(len(a.array[i:]))
} else {
for _, v := range a.array[i:] {
output.add(v)
}
}
}
return output
}
// differenceBitmapRun computes the difference of an bitmap from a run.
func differenceBitmapRun(a, b *Container) *Container {
statsHit("difference/BitmapRun")
if a.n == 0 || b.n == 0 {
return a.Clone()
}
output := a.Clone()
for j := 0; j < len(b.runs); j++ {
output.bitmapZeroRange(uint64(b.runs[j].start), uint64(b.runs[j].last)+1)
}
return output
}
// differenceRunArray subtracts the bits in an array container from a run
// container.
func differenceRunArray(a, b *Container) *Container {
statsHit("difference/RunArray")
if a.n == 0 || b.n == 0 {
return a.Clone()
}
output := &Container{runs: make([]interval16, 0, len(a.runs)), containerType: containerRun}
bidx := 0
vb := b.array[bidx]
RUNLOOP:
for _, run := range a.runs {
start := run.start
for vb < run.start {
bidx++
if bidx >= len(b.array) {
break
}
vb = b.array[bidx]
}
for vb >= run.start && vb <= run.last {
if vb == start {
if vb == 65535 { // overflow
break RUNLOOP
}
start++
bidx++
if bidx >= len(b.array) {
break
}
vb = b.array[bidx]
continue
}
output.runs = append(output.runs, interval16{start: start, last: vb - 1})
output.n += int32(vb - start)
if vb == 65535 { // overflow
break RUNLOOP
}
start = vb + 1
bidx++
if bidx >= len(b.array) {
break
}
vb = b.array[bidx]
}
if start <= run.last {
output.runs = append(output.runs, interval16{start: start, last: run.last})
output.n += int32(run.last - start + 1)
}
}
output.optimize()
return output
}
// differenceRunBitmap computes the difference of an run from a bitmap.
func differenceRunBitmap(a, b *Container) *Container {
statsHit("difference/RunBitmap")
// If a is full, difference is the flip of b.
if len(a.runs) > 0 && a.runs[0].start == 0 && a.runs[0].last == 65535 {
return flipBitmap(b)
}
output := &Container{containerType: containerRun}
output.n = a.n
if len(a.runs) == 0 {
return output
}
for j := 0; j < len(a.runs); j++ {
run := a.runs[j]
add := true
for bit := a.runs[j].start; bit <= a.runs[j].last; bit++ {
if b.bitmapContains(bit) {
output.n--
if run.start == bit {
if bit == 65535 { //overflow
add = false
}
run.start++
} else if bit == run.last {
run.last--
} else {
run.last = bit - 1
if run.last >= run.start {
output.runs = append(output.runs, run)
}
run.start = bit + 1
run.last = a.runs[j].last
}
if run.start > run.last {
break
}
}
if bit == 65535 { //overflow
break
}
}
if run.start <= run.last {
if add {
output.runs = append(output.runs, run)
}
}
}
if output.n < ArrayMaxSize && int32(len(output.runs)) > output.n/2 {
output.runToArray()
} else if len(output.runs) > runMaxSize {
output.runToBitmap()
}
return output
}
// differenceRunRun computes the difference of two runs.
func differenceRunRun(a, b *Container) *Container {
statsHit("difference/RunRun")
if a.n == 0 || b.n == 0 {
return a.Clone()
}
apos := 0 // current a-run index
bpos := 0 // current b-run index
astart := a.runs[apos].start
alast := a.runs[apos].last
bstart := b.runs[bpos].start
blast := b.runs[bpos].last
alen := len(a.runs)
blen := len(b.runs)
output := &Container{runs: make([]interval16, 0, alen+blen), containerType: containerRun} // TODO allocate max then truncate? or something else
// cardinality upper bound: sum of number of runs
// each B-run could split an A-run in two, up to len(b.runs) times
for apos < alen && bpos < blen {
switch {
case alast < bstart:
// current A-run entirely precedes current B-run: keep full A-run, advance to next A-run
output.runs = append(output.runs, interval16{start: astart, last: alast})
apos++
if apos < alen {
astart = a.runs[apos].start
alast = a.runs[apos].last
}
case blast < astart:
// current B-run entirely precedes current A-run: advance to next B-run
bpos++
if bpos < blen {
bstart = b.runs[bpos].start
blast = b.runs[bpos].last
}
default:
// overlap
if astart < bstart {
output.runs = append(output.runs, interval16{start: astart, last: bstart - 1})
}
if alast > blast {
astart = blast + 1
} else {
apos++
if apos < alen {
astart = a.runs[apos].start
alast = a.runs[apos].last
}
}
}
}
if apos < alen {
output.runs = append(output.runs, interval16{start: astart, last: alast})
apos++
if apos < alen {
output.runs = append(output.runs, a.runs[apos:]...)
}
}
output.n = output.count()
return output
}
func differenceArrayBitmap(a, b *Container) *Container {
statsHit("difference/ArrayBitmap")
output := &Container{containerType: containerArray}
for _, va := range a.array {
bmidx := va / 64
bidx := va % 64
mask := uint64(1) << bidx
b := b.bitmap[bmidx]
if mask&^b > 0 {
output.array = append(output.array, va)
}
}
output.n = int32(len(output.array))
return output
}
func differenceBitmapArray(a, b *Container) *Container {
statsHit("difference/BitmapArray")
output := a.Clone()
for _, v := range b.array {
if output.bitmapContains(v) {
output.bitmap[v/64] &^= (uint64(1) << uint(v%64))
output.n--
}
}
if output.n < ArrayMaxSize {
output.bitmapToArray()
}
return output
}
func differenceBitmapBitmap(a, b *Container) *Container {
statsHit("difference/BitmapBitmap")
// 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]
buf = make([]uint64, bitmapN)
ob = buf[:bitmapN]
n int32
)
for i := 0; i < bitmapN; i++ {
ob[i] = ab[i] & (^bb[i])
n += int32(popcount(ob[i]))
}
output := &Container{
bitmap: ob,
n: n,
containerType: containerBitmap,
}
if output.n < ArrayMaxSize {
output.bitmapToArray()
}
return output
}
func xor(a, b *Container) *Container {
if a.isArray() {
if b.isArray() {
return xorArrayArray(a, b)
} else if b.isRun() {
return xorArrayRun(a, b)
} else {
return xorArrayBitmap(a, b)
}
} else if a.isRun() {
if b.isArray() {
return xorArrayRun(b, a)
} else if b.isRun() {
return xorRunRun(a, b)
} else {
return xorBitmapRun(b, a)
}
} else {
if b.isArray() {
return xorArrayBitmap(b, a)
} else if b.isRun() {
return xorBitmapRun(a, b)
} else {
return xorBitmapBitmap(a, b)
}
}
}
func xorArrayArray(a, b *Container) *Container {
statsHit("xor/ArrayArray")
output := &Container{containerType: containerArray}
na, nb := len(a.array), len(b.array)
for i, j := 0, 0; i < na || j < nb; {
if i < na && j >= nb {
output.add(a.array[i])
i++
continue
} else if i >= na && j < nb {
output.add(b.array[j])
j++
continue
}
va, vb := a.array[i], b.array[j]
if va < vb {
output.add(va)
i++
} else if va > vb {
output.add(vb)
j++
} else { //==
i++
j++
}
}
return output
}
func xorArrayBitmap(a, b *Container) *Container {
statsHit("xor/ArrayBitmap")
output := b.Clone()
for _, v := range a.array {
if b.bitmapContains(v) {
output.remove(v)
} else {
output.add(v)
}
}
// It's possible that output was converted from bitmap to array in output.remove()
// so we only do this conversion if output is still a bitmap container.
if output.containerType == containerBitmap && output.count() < ArrayMaxSize {
output.bitmapToArray()
}
return output
}
func xorBitmapBitmap(a, b *Container) *Container {
statsHit("xor/BitmapBitmap")
// 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]
buf = make([]uint64, bitmapN)
ob = buf[:bitmapN]
n int32
)
for i := 0; i < bitmapN; i++ {
ob[i] = ab[i] ^ bb[i]
n += int32(popcount(ob[i]))
}
output := &Container{
bitmap: ob,
n: n,
containerType: containerBitmap,
}
if output.count() < ArrayMaxSize {
output.bitmapToArray()
}
return output
}
// opType represents a type of operation.
type opType uint8
const (
opTypeAdd = opType(0)
opTypeRemove = opType(1)
)
// op represents an operation on the bitmap.
type op struct {
typ opType
value uint64
}
// apply executes the operation against a bitmap.
func (op *op) apply(b *Bitmap) bool {
switch op.typ {
case opTypeAdd:
return b.DirectAdd(op.value)
case opTypeRemove:
return b.remove(op.value)
default:
panic(fmt.Sprintf("invalid op type: %d", op.typ))
}
}
// WriteTo writes op to the w.
func (op *op) WriteTo(w io.Writer) (n int64, err error) {
buf := make([]byte, op.size())
// Write type and value.
buf[0] = byte(op.typ)
binary.LittleEndian.PutUint64(buf[1:9], op.value)
// Add checksum at the end.
h := fnv.New32a()
h.Write(buf[0:9])
binary.LittleEndian.PutUint32(buf[9:13], h.Sum32())
// Write to writer.
nn, err := w.Write(buf)
return int64(nn), err
}
// UnmarshalBinary decodes data into an op.
func (op *op) UnmarshalBinary(data []byte) error {
if len(data) < op.size() {
return fmt.Errorf("op data out of bounds: len=%d", len(data))
}
statsHit("op/UnmarshalBinary")
// Verify checksum.
h := fnv.New32a()
h.Write(data[0:9])
if chk := binary.LittleEndian.Uint32(data[9:13]); chk != h.Sum32() {
return fmt.Errorf("checksum mismatch: exp=%08x, got=%08x", h.Sum32(), chk)
}
// Read type and value.
op.typ = opType(data[0])
op.value = binary.LittleEndian.Uint64(data[1:9])
return nil
}
// size returns the encoded size of the op, in bytes.
func (*op) size() int { return 1 + 8 + 4 }
func highbits(v uint64) uint64 { return v >> 16 }
func lowbits(v uint64) uint16 { return uint16(v & 0xFFFF) }
// search32 returns the index of value in a. If value is not found, it works the
// same way as search64.
func search32(a []uint16, value uint16) int32 {
statsHit("search32")
// Optimize for elements and the last element.
n := int32(len(a))
if n == 0 {
return -1
} else if a[n-1] == value {
return n - 1
}
// Otherwise perform binary search for exact match.
lo, hi := int32(0), n-1
for lo+16 <= hi {
i := int32(uint((lo + hi)) >> 1)
v := a[i]
if v < value {
lo = i + 1
} else if v > value {
hi = i - 1
} else {
return i
}
}
// If an exact match isn't found then return a negative index.
for ; lo <= hi; lo++ {
v := a[lo]
if v == value {
return lo
} else if v > value {
break
}
}
return -(lo + 1)
}
// search64 returns the index of value in a. If value is not found, -1 * (1 +
// the index where v would be if it were inserted) is returned. This is done in
// order to both signal that value was not found (negative number), and also
// return information about where v would go if it were inserted. The +1 offset
// is necessary due to the case where v is not found, but would go at index 0.
// since negative 0 is no different from positive 0, we offset the returned
// negative indices by 1. See the test for this function for examples.
func search64(a []uint64, value uint64) int {
statsHit("search64")
// Optimize for elements and the last element.
n := len(a)
if n == 0 {
return -1
} else if a[n-1] == value {
return n - 1
}
// Otherwise perform binary search for exact match.
lo, hi := 0, n-1
for lo+16 <= hi {
i := int(uint((lo + hi)) >> 1)
v := a[i]
if v < value {
lo = i + 1
} else if v > value {
hi = i - 1
} else {
return i
}
}
// If an exact match isn't found then return a negative index.
for ; lo <= hi; lo++ {
v := a[lo]
if v == value {
return lo
} else if v > value {
break
}
}
return -(lo + 1)
}
// trailingZeroN returns the number of trailing zeros in v.
// v must be greater than zero.
func trailingZeroN(v uint64) int {
return bits.TrailingZeros64(v)
}
// ErrorList represents a list of errors.
type ErrorList []error
func (a ErrorList) Error() string {
switch len(a) {
case 0:
return "no errors"
case 1:
return a[0].Error()
}
return fmt.Sprintf("%s (and %d more errors)", a[0], len(a)-1)
}
// Append appends an error to the list. If err is an ErrorList then all errors are appended.
func (a *ErrorList) Append(err error) {
switch err := err.(type) {
case ErrorList:
*a = append(*a, err...)
default:
*a = append(*a, err)
}
}
// AppendWithPrefix appends an error to the list and includes a prefix.
func (a *ErrorList) AppendWithPrefix(err error, prefix string) {
switch err := err.(type) {
case ErrorList:
for i := range err {
*a = append(*a, fmt.Errorf("%s%s", prefix, err[i]))
}
default:
*a = append(*a, fmt.Errorf("%s%s", prefix, err))
}
}
// xorArrayRun computes the exclusive or of an array and a run container.
func xorArrayRun(a, b *Container) *Container {
statsHit("xor/ArrayRun")
output := &Container{containerType: containerRun}
na, nb := len(a.array), len(b.runs)
var vb interval16
var va uint16
lastI, lastJ := -1, -1
for i, j := 0, 0; i < na || j < nb; {
if i < na && i != lastI {
va = a.array[i]
}
if j < nb && j != lastJ {
vb = b.runs[j]
}
lastI = i
lastJ = j
if i < na && (j >= nb || va < vb.start) { //before
output.n += output.runAppendInterval(interval16{start: va, last: va})
i++
} else if j < nb && (i >= na || va > vb.last) { //after
output.n += output.runAppendInterval(vb)
j++
} else if va > vb.start {
if va < vb.last {
output.n += output.runAppendInterval(interval16{start: vb.start, last: va - 1})
i++
vb.start = va + 1
if vb.start > vb.last {
j++
}
} else if va > vb.last {
output.n += output.runAppendInterval(vb)
j++
} else { // va == vb.last
vb.last--
if vb.start <= vb.last {
output.n += output.runAppendInterval(vb)
}
j++
i++
}
} else { // we know va == vb.start
if vb.start == maxContainerVal { // protect overflow
j++
} else {
vb.start++
if vb.start > vb.last {
j++
}
}
i++
}
}
if output.n < ArrayMaxSize {
output.runToArray()
} else if len(output.runs) > runMaxSize {
output.runToBitmap()
}
return output
}
// xorCompare computes first exclusive run between two runs.
func xorCompare(x *xorstm) (r1 interval16, hasData bool) {
hasData = false
if !x.vaValid || !x.vbValid {
if x.vbValid {
x.vbValid = false
return x.vb, true
}
if x.vaValid {
x.vaValid = false
return x.va, true
}
return r1, false
}
if x.va.last < x.vb.start { //va before
x.vaValid = false
r1 = x.va
hasData = true
} else if x.vb.last < x.va.start { //vb before
x.vbValid = false
r1 = x.vb
hasData = true
} else if x.va.start == x.vb.start && x.va.last == x.vb.last { // Equal
x.vaValid = false
x.vbValid = false
} else if x.va.start <= x.vb.start && x.va.last >= x.vb.last { //vb inside
x.vbValid = false
if x.va.start != x.vb.start {
r1 = interval16{start: x.va.start, last: x.vb.start - 1}
hasData = true
}
if x.vb.last == maxContainerVal { // Check for overflow
x.vaValid = false
} else {
x.va.start = x.vb.last + 1
if x.va.start > x.va.last {
x.vaValid = false
}
}
} else if x.vb.start <= x.va.start && x.vb.last >= x.va.last { //va inside
x.vaValid = false
if x.vb.start != x.va.start {
r1 = interval16{start: x.vb.start, last: x.va.start - 1}
hasData = true
}
if x.va.last == maxContainerVal { //check for overflow
x.vbValid = false
} else {
x.vb.start = x.va.last + 1
if x.vb.start > x.vb.last {
x.vbValid = false
}
}
} else if x.va.start < x.vb.start && x.va.last <= x.vb.last { //va first overlap
x.vaValid = false
r1 = interval16{start: x.va.start, last: x.vb.start - 1}
hasData = true
if x.va.last == maxContainerVal { // check for overflow
x.vbValid = false
} else {
x.vb.start = x.va.last + 1
if x.vb.start > x.vb.last {
x.vbValid = false
}
}
} else if x.vb.start < x.va.start && x.vb.last <= x.va.last { //vb first overlap
x.vbValid = false
r1 = interval16{start: x.vb.start, last: x.va.start - 1}
hasData = true
if x.vb.last == maxContainerVal { // check for overflow
x.vaValid = false
} else {
x.va.start = x.vb.last + 1
if x.va.start > x.va.last {
x.vaValid = false
}
}
}
return r1, hasData
}
//stm is state machine used to "xor" iterate over runs.
type xorstm struct {
vaValid, vbValid bool
va, vb interval16
}
// xorRunRun computes the exclusive or of two run containers.
func xorRunRun(a, b *Container) *Container {
statsHit("xor/RunRun")
na, nb := len(a.runs), len(b.runs)
if na == 0 {
return b.Clone()
}
if nb == 0 {
return a.Clone()
}
output := &Container{containerType: containerRun}
lastI, lastJ := -1, -1
state := &xorstm{}
for i, j := 0, 0; i < na || j < nb; {
if i < na && lastI != i {
state.va = a.runs[i]
state.vaValid = true
}
if j < nb && lastJ != j {
state.vb = b.runs[j]
state.vbValid = true
}
lastI, lastJ = i, j
r1, ok := xorCompare(state)
if ok {
output.n += output.runAppendInterval(r1)
}
if !state.vaValid {
i++
}
if !state.vbValid {
j++
}
}
if output.n < ArrayMaxSize && int32(len(output.runs)) > output.n/2 {
output.runToArray()
} else if len(output.runs) > runMaxSize {
output.runToBitmap()
}
return output
}
// xorRunRun computes the exclusive or of a bitmap and a run container.
func xorBitmapRun(a, b *Container) *Container {
statsHit("xor/BitmapRun")
output := a.Clone()
for j := 0; j < len(b.runs); j++ {
output.bitmapXorRange(uint64(b.runs[j].start), uint64(b.runs[j].last)+1)
}
if output.n < ArrayMaxSize && int32(len(output.runs)) > output.n/2 {
output.runToArray()
} else if len(output.runs) > runMaxSize {
output.runToBitmap()
}
return output
}
func bitmapsEqual(b, c *Bitmap) error { // nolint: deadcode
statsHit("bitmapsEqual")
if b.OpWriter != c.OpWriter {
return errors.New("opWriters not equal")
}
if b.opN != c.opN {
return errors.New("opNs not equal")
}
biter, _ := b.Containers.Iterator(0)
citer, _ := c.Containers.Iterator(0)
bn, cn := biter.Next(), citer.Next()
for ; bn && cn; bn, cn = biter.Next(), citer.Next() {
bk, bc := biter.Value()
ck, cc := citer.Value()
if bk != ck {
return errors.New("keys not equal")
}
if !bc.equals(cc) {
return errors.New("containers not equal")
}
}
if bn && !cn || cn && !bn {
return errors.New("different numbers of containers")
}
return nil
}
func popcount(x uint64) uint64 {
return uint64(bits.OnesCount64(x))
}
func popcountAndSlice(s, m []uint64) uint64 {
var (
a = s[:bitmapN]
b = m[:bitmapN]
)
cnt := uint64(0)
for i := 0; i < bitmapN; i++ {
cnt += popcount(a[i] & b[i])
}
return cnt
}
// constants from github.com/RoaringBitmap/roaring
// taken from roaring/util.go
const (
serialCookieNoRunContainer = 12346 // only arrays and bitmaps
serialCookie = 12347 // runs, arrays, and bitmaps
)
func readOfficialHeader(buf []byte) (size uint32, containerTyper func(index uint, card int) byte, header, pos int, haveRuns bool, err error) {
statsHit("readOfficialHeader")
if len(buf) < 8 {
err = fmt.Errorf("buffer too small, expecting at least 8 bytes, was %d", len(buf))
return size, containerTyper, header, pos, haveRuns, err
}
cf := func(index uint, card int) (newType byte) {
newType = containerBitmap
if card < ArrayMaxSize {
newType = containerArray
}
return newType
}
containerTyper = cf
cookie := binary.LittleEndian.Uint32(buf)
pos += 4
// cookie header
if cookie == serialCookieNoRunContainer {
size = binary.LittleEndian.Uint32(buf[pos:])
pos += 4
} else if cookie&0x0000FFFF == serialCookie {
haveRuns = true
size = uint32(uint16(cookie>>16) + 1) // number of containers
// create is-run-container bitmap
isRunBitmapSize := (int(size) + 7) / 8
if pos+isRunBitmapSize > len(buf) {
err = fmt.Errorf("malformed bitmap, is-run bitmap overruns buffer at %d", pos+isRunBitmapSize)
return size, containerTyper, header, pos, haveRuns, err
}
isRunBitmap := buf[pos : pos+isRunBitmapSize]
pos += isRunBitmapSize
containerTyper = func(index uint, card int) byte {
if isRunBitmap[index/8]&(1<<(index%8)) != 0 {
return containerRun
}
return cf(index, card)
}
} else {
err = fmt.Errorf("did not find expected serialCookie in header")
return size, containerTyper, header, pos, haveRuns, err
}
header = pos
if size > (1 << 16) {
err = fmt.Errorf("It is logically impossible to have more than (1<<16) containers.")
return size, containerTyper, header, pos, haveRuns, err
}
// descriptive header
if pos+2*2*int(size) > len(buf) {
err = fmt.Errorf("malformed bitmap, key-cardinality slice overruns buffer at %d", pos+2*2*int(size))
return size, containerTyper, header, pos, haveRuns, err
}
pos += 2 * 2 * int(size) // moving pos past keycount
return size, containerTyper, header, pos, haveRuns, err
}
// UnmarshalBinary decodes b from a binary-encoded byte slice. data can be in
// either official roaring format or Pilosa's roaring format.
func (b *Bitmap) UnmarshalBinary(data []byte) error {
if data == nil {
// Nothing to unmarshal
return nil
}
statsHit("Bitmap/UnmarshalBinary")
fileMagic := uint32(binary.LittleEndian.Uint16(data[0:2]))
if fileMagic == magicNumber { // if pilosa roaring
return errors.Wrap(b.unmarshalPilosaRoaring(data), "unmarshaling as pilosa roaring")
}
keyN, containerTyper, header, pos, haveRuns, err := readOfficialHeader(data)
if err != nil {
return errors.Wrap(err, "reading roaring header")
}
b.Containers.Reset()
// Descriptive header section: Read container keys and cardinalities.
for i, buf := uint(0), data[header:]; i < uint(keyN); i, buf = i+1, buf[4:] {
card := int(binary.LittleEndian.Uint16(buf[2:4])) + 1
b.Containers.PutContainerValues(
uint64(binary.LittleEndian.Uint16(buf[0:2])),
containerTyper(i, card), /// container type voodo with isRunBitmap
card,
true)
}
// Read container offsets and attach data.
if haveRuns {
readWithRuns(b, data, pos, keyN)
} else {
err := readOffsets(b, data, pos, keyN)
if err != nil {
return errors.Wrap(err, "reading offsets from official roaring format")
}
}
return nil
}
func readOffsets(b *Bitmap, data []byte, pos int, keyN uint32) error {
citer, _ := b.Containers.Iterator(0)
for i, buf := 0, data[pos:]; i < int(keyN); i, buf = i+1, buf[4:] {
offset := binary.LittleEndian.Uint32(buf[0:4])
// Verify the offset is within the bounds of the input data.
if int(offset) >= len(data) {
return fmt.Errorf("offset out of bounds: off=%d, len=%d", offset, len(data))
}
// Map byte slice directly to the container data.
citer.Next()
_, c := citer.Value()
switch c.containerType {
case containerArray:
c.runs = nil
c.bitmap = nil
c.array = (*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[offset]))[:c.n]
case containerBitmap:
c.array = nil
c.runs = nil
c.bitmap = (*[0xFFFFFFF]uint64)(unsafe.Pointer(&data[offset]))[:bitmapN]
default:
return fmt.Errorf("unsupported container type %d", c.containerType)
}
}
return nil
}
func readWithRuns(b *Bitmap, data []byte, pos int, keyN uint32) {
citer, _ := b.Containers.Iterator(0)
for i := 0; i < int(keyN); i++ {
citer.Next()
_, c := citer.Value()
switch c.containerType {
case containerRun:
c.array = nil
c.bitmap = nil
runCount := binary.LittleEndian.Uint16(data[pos : pos+runCountHeaderSize])
c.runs = (*[0xFFFFFFF]interval16)(unsafe.Pointer(&data[pos+runCountHeaderSize]))[:runCount]
for o := range c.runs { // must convert from start:length to start:end :(
c.runs[o].last = c.runs[o].start + c.runs[o].last
}
pos += int((runCount * interval16Size) + runCountHeaderSize)
case containerArray:
c.runs = nil
c.bitmap = nil
c.array = (*[0xFFFFFFF]uint16)(unsafe.Pointer(&data[pos]))[:c.n]
pos += int(c.n * 2)
case containerBitmap:
c.array = nil
c.runs = nil
c.bitmap = (*[0xFFFFFFF]uint64)(unsafe.Pointer(&data[pos]))[:bitmapN]
pos += bitmapN * 8
}
}
}