Merge pull request #258 from kuba--/intersect-inplace

The first implementation of intersect in place
This commit is contained in:
Kuba Podgórski 2020-05-07 11:23:40 +02:00 committed by GitHub
commit 5578eb8daa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 1114 additions and 81 deletions

View file

@ -77,8 +77,7 @@ func (sc *sliceContainers) Remove(key uint64) {
return
}
if key == sc.lastKey {
sc.lastKey = ^uint64(0)
sc.lastContainer = nil
sc.invalidateCache()
}
sc.keys = append(sc.keys[:i], sc.keys[i+1:]...)
sc.containers = append(sc.containers[:i], sc.containers[i+1:]...)
@ -159,8 +158,7 @@ func (sc *sliceContainers) Count() uint64 {
func (sc *sliceContainers) Reset() {
sc.keys = sc.keys[:0]
sc.containers = sc.containers[:0]
sc.lastContainer = nil
sc.lastKey = ^uint64(0)
sc.invalidateCache()
}
func (sc *sliceContainers) ResetN(n int) {
@ -171,8 +169,7 @@ func (sc *sliceContainers) ResetN(n int) {
sc.keys = sc.keys[:0]
sc.containers = sc.containers[:0]
}
sc.lastContainer = nil
sc.lastKey = ^uint64(0)
sc.invalidateCache()
}
func (sc *sliceContainers) seek(key uint64) (int, bool) {
@ -187,13 +184,28 @@ func (sc *sliceContainers) seek(key uint64) (int, bool) {
func (sc *sliceContainers) Iterator(key uint64) (citer ContainerIterator, found bool) {
i, found := sc.seek(key)
return &sliceIterator{e: sc, i: i}, found
return &sliceIterator{e: sc, i: i, index: i}, found
}
// Repair tries to repair all containers,
// results in nil and empty containers getting dropped from the slice.
// For instance, that has to happen for writing the roaring format,
// which can't represent an empty container (c.N() == 0).
func (sc *sliceContainers) Repair() {
for _, c := range sc.containers {
n := 0
for i, c := range sc.containers {
if c == nil {
continue
}
c.Repair()
sc.containers[n] = c
sc.keys[n] = sc.keys[i]
n++
}
sc.containers = sc.containers[:n]
sc.keys = sc.keys[:n]
sc.invalidateCache()
}
// Update calls fn (existing-container, existed), and expects
@ -228,27 +240,35 @@ func (sc *sliceContainers) UpdateEvery(fn func(uint64, *Container, bool) (*Conta
sc.containers[i] = nc
}
}
// invalidate cache.
sc.invalidateCache()
}
func (sc *sliceContainers) invalidateCache() {
sc.lastKey = ^uint64(0)
sc.lastContainer = nil
}
type sliceIterator struct {
e *sliceContainers
i int
key uint64
value *Container
i int // next e's index to get key, value
index int // current e's index of key, value
key uint64 // current key
value *Container // current value
}
func (si *sliceIterator) Next() bool {
if si.e == nil {
return false
}
// discard nil containers from iteration. we don't always
// actually remove them because copying is expensive.
for si.i < len(si.e.keys) {
si.key = si.e.keys[si.i]
si.value = si.e.containers[si.i]
// keep the current index of key, value
si.index = si.i
si.i++
if si.value != nil {
return true

View file

@ -99,3 +99,89 @@ func testContainersIterator(cs Containers, t *testing.T) {
t.Fatalf("itr should be done, but got true")
}
}
func TestSliceContainers(t *testing.T) {
const size = 10
n := size
sc := newSliceContainers()
// Add n keys
for i := 0; i < n; i++ {
key, set := uint64(i), []uint16{uint16(i)}
sc.Put(key, NewContainerArray(set))
}
t.Run("Get n keys", func(t *testing.T) {
for i := 0; i < n; i++ {
key, set := uint64(i), []uint16{uint16(i)}
c := sc.Get(key)
if c == nil {
t.Fatalf("Get(%d) returned nil container", key)
}
if c.data[0] != set[0] {
t.Fatalf("Get(%d): expected: %v, got: %v", key, set[0], c.data[0])
}
}
})
t.Run("Last key/container", func(t *testing.T) {
key, c := sc.Last()
if key != uint64(n-1) || c.data[0] != uint16(n-1) {
t.Fatalf("Last: expected: %v, got: %d, %v", n-1, key, c.data)
}
})
// Remove odd keys
for i := 1; i < size; i += 2 {
key := uint64(i)
sc.Remove(key)
n--
}
t.Run("Try to Get removed containers", func(t *testing.T) {
for i := 1; i < size; i += 2 {
key := uint64(i)
c := sc.Get(key)
if c != nil {
t.Fatalf("Get(for non existing key %d): found container: %v", key, c.data)
}
}
// Test - Last key/container
key, c := sc.Last()
if key != uint64(size-2) || c.data[0] != uint16(size-2) {
t.Fatalf("Last: expected: %v, got: %d, %v", size-2, key, c.data)
}
if sc.Size() != n {
t.Fatalf("Size: expected: %d, got: %d", n, sc.Size())
}
})
t.Run("Nil containers and repair them", func(t *testing.T) {
// Remove half of even containers
for i := range sc.containers {
if i%2 == 0 {
sc.containers[i] = nil
n--
}
}
sc.Repair()
if sc.Size() != n {
t.Fatalf("Size: expected: %d, got: %d", n, sc.Size())
}
for i, key := range sc.keys {
if sc.containers[i] == nil {
t.Fatalf("Found nil container for key: %d at index: %d", key, i)
} else {
if sc.containers[i].data[0] != uint16(key) {
t.Fatalf("Invalid container data for key: %d at index: %d - expected: %d, got: %d",
key, i, uint16(key), sc.containers[i].data[0],
)
}
}
}
})
}

View file

@ -694,6 +694,474 @@ func (b *Bitmap) Intersect(other *Bitmap) *Bitmap {
return output
}
type mutableContainersIterator struct {
c Containers
cit ContainerIterator
sit *sliceIterator
}
func newMutableContainersIterator(cs Containers, key uint64) *mutableContainersIterator {
it := &mutableContainersIterator{c: cs}
if sc, ok := cs.(*sliceContainers); ok {
if i, found := sc.seek(key); found {
it.sit = &sliceIterator{e: sc, i: i, index: i}
return it
}
}
it.cit, _ = cs.Iterator(key)
return it
}
func (it *mutableContainersIterator) update(key uint64, newCont *Container) {
if it.sit != nil {
if key == it.sit.key {
it.sit.e.containers[it.sit.index] = newCont
}
}
it.c.Update(key, func(_ *Container, _ bool) (*Container, bool) {
return newCont, true
})
}
func (it *mutableContainersIterator) Next() bool {
if it.sit != nil {
return it.sit.Next()
}
return it.cit.Next()
}
func (it *mutableContainersIterator) Value() (uint64, *Container) {
if it.sit != nil {
return it.sit.Value()
}
return it.cit.Value()
}
// IntersectInPlace returns the bitwise intersection of b and others,
// modifying b in place.
func (b *Bitmap) IntersectInPlace(others ...*Bitmap) {
var bSize int
if bSize = b.Size(); bSize == 0 {
// If b doesn't have any containers then return early.
return
}
otherIters := make(handledIters, 0, len(others))
for _, other := range others {
it, _ := other.Containers.Iterator(0)
if !it.Next() {
// An empty bitmap - reset all
b.Containers.Reset()
return
}
otherIters = append(otherIters, handledIter{
iter: it,
hasNext: true,
})
}
bIter := newMutableContainersIterator(b.Containers, 0)
for bIter.Next() {
bKey, bCont := bIter.Value()
if bCont.N() == 0 {
// No point in intersecting things from an empty container.
bIter.update(bKey, nil)
continue
}
// Loop until every iters current value has been handled.
for _, otherIter := range otherIters {
if !otherIter.hasNext {
continue
}
otherKey, otherCont := otherIter.iter.Value()
for otherKey < bKey {
otherIter.hasNext = otherIter.iter.Next()
if !otherIter.hasNext {
break
}
otherKey, otherCont = otherIter.iter.Value()
}
if bKey == otherKey {
// Note: a nil container is valid, and has N == 0.
if otherCont.N() != 0 {
if bCont.frozen() {
bCont = bCont.Clone()
b.Containers.Put(bKey, bCont)
}
bCont = bCont.intersectInPlace(otherCont)
bIter.update(bKey, bCont)
if bCont == nil || bCont.N() == 0 {
break
}
otherIter.hasNext = otherIter.iter.Next()
continue
}
}
bIter.update(bKey, nil)
break
}
}
b.Containers.Repair()
}
func (c *Container) intersectInPlace(other *Container) *Container {
// short-circuit the trivial cases
if c == nil || other == nil || c.N() == 0 || other.N() == 0 {
c = nil
return c
}
cFull, otherFull := (c.N() == maxContainerVal+1), (other.N() == maxContainerVal+1)
if cFull && otherFull {
return c
}
if cFull {
return c.copyInPlace(other)
}
if otherFull {
return c
}
switch c.typ() {
case containerArray:
switch other.typ() {
case containerArray:
return intersectArrayArrayInPlace(c, other)
case containerBitmap:
return intersectArrayBitmapInPlace(c, other)
case containerRun:
return intersectArrayRunInPlace(c, other)
}
case containerBitmap:
switch other.typ() {
case containerArray:
return intersectBitmapArrayInPlace(c, other)
case containerBitmap:
return intersectBitmapBitmapInPlace(c, other)
case containerRun:
return intersectBitmapRunInPlace(c, other)
}
case containerRun:
switch other.typ() {
case containerArray:
return intersectRunArrayInPlace(c, other)
case containerBitmap:
return intersectRunBitmapInPlace(c, other)
case containerRun:
return intersectRunRunInPlace(c, other)
}
}
if roaringParanoia {
panic(fmt.Sprintf("invalid intersect op: unknown types %d/%d", c.typ(), other.typ()))
}
return nil
}
func (c *Container) copyInPlace(other *Container) *Container {
switch other.typ() {
case containerArray:
c.setTyp(containerArray)
c.setArrayMaybeCopy(other.array(), true)
c.setN(other.N())
case containerBitmap:
bmp := make([]uint64, bitmapN)
copy(bmp, other.bitmap())
c.setTyp(containerBitmap)
c.setBitmap(bmp)
c.setN(other.N())
case containerRun:
c.setTyp(containerRun)
c.setRunsMaybeCopy(other.runs(), true)
c.setN(other.N())
}
return c
}
func intersectArrayArrayInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/ArrayArray")
a = a.Thaw()
aa, ba := a.array(), b.array()
an, bn := len(aa), len(ba)
n := 0
for i, j := 0, 0; i < an && j < bn; {
va, vb := aa[i], ba[j]
if va < vb {
i++
} else if va > vb {
j++
} else {
aa[n] = va
n, i, j = n+1, i+1, j+1
}
}
aa = aa[:n]
a.setArray(aa)
return a
}
func intersectArrayRunInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/ArrayRun")
a = a.Thaw()
aa, br := a.array(), b.runs()
an, bn := len(aa), len(br)
n := 0
for i, j := 0, 0; i < an && j < bn; {
va, vb := aa[i], br[j]
if va < vb.start {
i++
} else if va > vb.last {
j++
} else {
aa[n] = va
n++
i++
}
}
aa = aa[:n]
a.setArray(aa)
return a
}
func intersectArrayBitmapInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/ArrayBitmap")
a = a.Thaw()
aa := a.array()
bb := b.bitmap()
n := 0
for _, va := range aa {
b := bb[va>>6]
bidx := va % 64
mask := uint64(1) << bidx
if b&mask > 0 {
aa[n] = va
n++
}
}
aa = aa[:n]
a.setArray(aa)
return a
}
func intersectBitmapBitmapInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/BitmapBitmap")
a = a.Thaw()
ab := a.bitmap()[:bitmapN]
bb := b.bitmap()[:bitmapN]
n := int32(0)
for i := 0; i < bitmapN; i += 4 {
// unrolling is still effective in go
ptr := (*[4]uint64)(unsafe.Pointer(&bb[i]))
ab[i] &= ptr[0]
ab[i+1] &= ptr[1]
ab[i+2] &= ptr[2]
ab[i+3] &= ptr[3]
n += int32(popcount(ab[i])) +
int32(popcount(ab[i+1])) +
int32(popcount(ab[i+2])) +
int32(popcount(ab[i+3]))
}
a.setN(n)
return a
}
func intersectBitmapArrayInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/BitmapArray")
a = a.Thaw()
ab := a.bitmap()
ba := b.array()
bn := len(ba)
array := make([]uint16, bn)
n := int32(0)
for _, vb := range ba {
i := vb >> 6
mask := uint64(1) << uint(vb%64)
if ab[i]&mask > 0 {
array[n] = vb
n++
}
}
array = array[:n]
a.setTyp(containerArray)
a.setArray(array)
a.setN(n)
return a
}
func intersectBitmapRunInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/BitmapRun")
a = a.Thaw()
ab := a.bitmap()
br := b.runs()
an := len(ab)
bitmap := make([]uint64, an)
n := int32(0)
for _, vb := range br {
i := vb.start >> 6 // index into a
vastart := i << 6
valast := vastart + 63
for valast >= vb.start && vastart <= vb.last && int(i) < an {
if vastart >= vb.start && valast <= vb.last { // a within b
bitmap[i] = ab[i]
n += int32(popcount(ab[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 := ab[i] & mask
bitmap[i] |= bits
n += int32(popcount(bits))
} else if vastart < vb.start { // a overlaps front of b
offset := 64 - (1 + valast - vb.start)
bits := (ab[i] >> offset) << offset
bitmap[i] |= bits
n += int32(popcount(bits))
} else if vb.start < vastart { // b overlaps front of a
offset := 64 - (1 + vb.last - vastart)
bits := (ab[i] << offset) >> offset
bitmap[i] |= bits
n += int32(popcount(bits))
}
i++
vastart = i << 6
valast = vastart + 63
}
}
a.setBitmap(bitmap)
a.setN(n)
return a
}
func intersectRunRunInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/RunRun")
a = a.Thaw()
ar, br := a.runs(), b.runs()
an, bn := len(ar), len(br)
var runs []interval16
if an > bn {
runs = make([]interval16, 0, an)
} else {
runs = make([]interval16, 0, bn)
}
n := int32(0)
for i, j := 0, 0; i < an && j < bn; {
va, vb := ar[i], br[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--|
runs = append(runs, interval16{start: va.start, last: vb.last})
n += int32(vb.last-va.start) + 1
j++
} else if va.last > vb.last && va.start < vb.start {
// |--va|--vb--|--|
runs = append(runs, vb)
n += int32(vb.last-vb.start) + 1
j++
} else if va.last <= vb.last && va.start >= vb.start {
// |--vb|--va--|--|
runs = append(runs, va)
n += int32(va.last-va.start) + 1
i++
} else if va.last <= vb.last && va.start < vb.start {
// |--va-|-|-vb--|
runs = append(runs, interval16{start: vb.start, last: va.last})
n += int32(va.last-vb.start) + 1
i++
}
}
a.setRuns(runs)
a.setN(n)
return a
}
func intersectRunArrayInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/RunArray")
a = a.Thaw()
ar, ba := a.runs(), b.array()
an, bn := len(ar), len(ba)
array := make([]uint16, bn)
n := 0
for i, j := 0, 0; i < an && j < bn; {
va, vb := ar[i], ba[j]
if vb < va.start {
j++
} else if vb > va.last {
i++
} else {
array[n] = vb
n++
j++
}
}
array = array[:n]
a.setTyp(containerArray)
a.setArray(array)
return a
}
func intersectRunBitmapInPlace(a, b *Container) *Container {
statsHit("intersectInPlace/RunBitmap")
// TODO(@kuba--):
// Figure out how to efficiently intersect dense runs with bitmaps.
// So far we convert run to bitmap and intersect bitmaps - it's much faster
// than naive O(n^2) algorithm.
a = a.runToBitmap()
return intersectBitmapBitmapInPlace(a, b)
}
// Union returns the bitwise union of b and others as a new bitmap.
func (b *Bitmap) Union(others ...*Bitmap) *Bitmap {
if len(others) == 1 {

View file

@ -2754,6 +2754,15 @@ func differenceInPlaceWrapper(a, b *Container) *Container {
return out.Containers.Get(0)
}
func intersectInPlaceWrapper(a, b *Container) *Container {
out := NewBitmap()
out.Containers.Put(0, a.Clone())
B := NewBitmap()
B.Containers.Put(0, b)
out.IntersectInPlace(B)
return out.Containers.Get(0)
}
func TestContainerCombinations(t *testing.T) {
cts := setupContainerTests()
@ -2872,6 +2881,109 @@ func TestContainerCombinations(t *testing.T) {
{intersect, "evenBitsSet", "oddBitsSet", "empty"},
{intersect, "evenBitsSet", "evenBitsSet", "evenBitsSet"},
// intersect in place
{intersectInPlaceWrapper, "empty", "empty", "empty"},
{intersectInPlaceWrapper, "empty", "full", "empty"},
{intersectInPlaceWrapper, "empty", "firstBitSet", "empty"},
{intersectInPlaceWrapper, "empty", "lastBitSet", "empty"},
{intersectInPlaceWrapper, "empty", "firstBitUnset", "empty"},
{intersectInPlaceWrapper, "empty", "lastBitUnset", "empty"},
{intersectInPlaceWrapper, "empty", "innerBitsSet", "empty"},
{intersectInPlaceWrapper, "empty", "outerBitsSet", "empty"},
{intersectInPlaceWrapper, "empty", "oddBitsSet", "empty"},
{intersectInPlaceWrapper, "empty", "evenBitsSet", "empty"},
//
{intersectInPlaceWrapper, "full", "empty", "empty"},
{intersectInPlaceWrapper, "full", "full", "full"},
{intersectInPlaceWrapper, "full", "firstBitSet", "firstBitSet"},
{intersectInPlaceWrapper, "full", "lastBitSet", "lastBitSet"},
{intersectInPlaceWrapper, "full", "firstBitUnset", "firstBitUnset"},
{intersectInPlaceWrapper, "full", "lastBitUnset", "lastBitUnset"},
{intersectInPlaceWrapper, "full", "innerBitsSet", "innerBitsSet"},
{intersectInPlaceWrapper, "full", "outerBitsSet", "outerBitsSet"},
{intersectInPlaceWrapper, "full", "oddBitsSet", "oddBitsSet"},
{intersectInPlaceWrapper, "full", "evenBitsSet", "evenBitsSet"},
//
{intersectInPlaceWrapper, "firstBitSet", "empty", "empty"},
{intersectInPlaceWrapper, "firstBitSet", "full", "firstBitSet"},
{intersectInPlaceWrapper, "firstBitSet", "firstBitSet", "firstBitSet"},
{intersectInPlaceWrapper, "firstBitSet", "lastBitSet", "empty"},
{intersectInPlaceWrapper, "firstBitSet", "firstBitUnset", "empty"},
{intersectInPlaceWrapper, "firstBitSet", "lastBitUnset", "firstBitSet"},
{intersectInPlaceWrapper, "firstBitSet", "innerBitsSet", "empty"},
{intersectInPlaceWrapper, "firstBitSet", "outerBitsSet", "firstBitSet"},
{intersectInPlaceWrapper, "firstBitSet", "oddBitsSet", "empty"},
{intersectInPlaceWrapper, "firstBitSet", "evenBitsSet", "firstBitSet"},
//
{intersectInPlaceWrapper, "lastBitSet", "empty", "empty"},
{intersectInPlaceWrapper, "lastBitSet", "full", "lastBitSet"},
{intersectInPlaceWrapper, "lastBitSet", "firstBitSet", "empty"},
{intersectInPlaceWrapper, "lastBitSet", "lastBitSet", "lastBitSet"},
{intersectInPlaceWrapper, "lastBitSet", "firstBitUnset", "lastBitSet"},
{intersectInPlaceWrapper, "lastBitSet", "lastBitUnset", "empty"},
{intersectInPlaceWrapper, "lastBitSet", "innerBitsSet", "empty"},
{intersectInPlaceWrapper, "lastBitSet", "outerBitsSet", "lastBitSet"},
{intersectInPlaceWrapper, "lastBitSet", "oddBitsSet", "lastBitSet"},
{intersectInPlaceWrapper, "lastBitSet", "evenBitsSet", "empty"},
//
{intersectInPlaceWrapper, "firstBitUnset", "empty", "empty"},
{intersectInPlaceWrapper, "firstBitUnset", "full", "firstBitUnset"},
{intersectInPlaceWrapper, "firstBitUnset", "firstBitSet", "empty"},
{intersectInPlaceWrapper, "firstBitUnset", "lastBitSet", "lastBitSet"},
{intersectInPlaceWrapper, "firstBitUnset", "firstBitUnset", "firstBitUnset"},
{intersectInPlaceWrapper, "firstBitUnset", "lastBitUnset", "innerBitsSet"},
{intersectInPlaceWrapper, "firstBitUnset", "innerBitsSet", "innerBitsSet"},
{intersectInPlaceWrapper, "firstBitUnset", "outerBitsSet", "lastBitSet"},
{intersectInPlaceWrapper, "firstBitUnset", "oddBitsSet", "oddBitsSet"},
//
{intersectInPlaceWrapper, "lastBitUnset", "empty", "empty"},
{intersectInPlaceWrapper, "lastBitUnset", "full", "lastBitUnset"},
{intersectInPlaceWrapper, "lastBitUnset", "firstBitSet", "firstBitSet"},
{intersectInPlaceWrapper, "lastBitUnset", "lastBitSet", "empty"},
{intersectInPlaceWrapper, "lastBitUnset", "firstBitUnset", "innerBitsSet"},
{intersectInPlaceWrapper, "lastBitUnset", "lastBitUnset", "lastBitUnset"},
{intersectInPlaceWrapper, "lastBitUnset", "innerBitsSet", "innerBitsSet"},
{intersectInPlaceWrapper, "lastBitUnset", "outerBitsSet", "firstBitSet"},
{intersectInPlaceWrapper, "lastBitUnset", "evenBitsSet", "evenBitsSet"},
//
{intersectInPlaceWrapper, "innerBitsSet", "empty", "empty"},
{intersectInPlaceWrapper, "innerBitsSet", "full", "innerBitsSet"},
{intersectInPlaceWrapper, "innerBitsSet", "firstBitSet", "empty"},
{intersectInPlaceWrapper, "innerBitsSet", "lastBitSet", "empty"},
{intersectInPlaceWrapper, "innerBitsSet", "firstBitUnset", "innerBitsSet"},
{intersectInPlaceWrapper, "innerBitsSet", "lastBitUnset", "innerBitsSet"},
{intersectInPlaceWrapper, "innerBitsSet", "innerBitsSet", "innerBitsSet"},
{intersectInPlaceWrapper, "innerBitsSet", "outerBitsSet", "empty"},
//
{intersectInPlaceWrapper, "outerBitsSet", "empty", "empty"},
{intersectInPlaceWrapper, "outerBitsSet", "full", "outerBitsSet"},
{intersectInPlaceWrapper, "outerBitsSet", "firstBitSet", "firstBitSet"},
{intersectInPlaceWrapper, "outerBitsSet", "lastBitSet", "lastBitSet"},
{intersectInPlaceWrapper, "outerBitsSet", "firstBitUnset", "lastBitSet"},
{intersectInPlaceWrapper, "outerBitsSet", "lastBitUnset", "firstBitSet"},
{intersectInPlaceWrapper, "outerBitsSet", "innerBitsSet", "empty"},
{intersectInPlaceWrapper, "outerBitsSet", "outerBitsSet", "outerBitsSet"},
{intersectInPlaceWrapper, "outerBitsSet", "oddBitsSet", "lastBitSet"},
{intersectInPlaceWrapper, "outerBitsSet", "evenBitsSet", "firstBitSet"},
//
{intersectInPlaceWrapper, "oddBitsSet", "empty", "empty"},
{intersectInPlaceWrapper, "oddBitsSet", "full", "oddBitsSet"},
{intersectInPlaceWrapper, "oddBitsSet", "firstBitSet", "empty"},
{intersectInPlaceWrapper, "oddBitsSet", "lastBitSet", "lastBitSet"},
{intersectInPlaceWrapper, "oddBitsSet", "firstBitUnset", "oddBitsSet"},
{intersectInPlaceWrapper, "oddBitsSet", "outerBitsSet", "lastBitSet"},
{intersectInPlaceWrapper, "oddBitsSet", "oddBitsSet", "oddBitsSet"},
{intersectInPlaceWrapper, "oddBitsSet", "evenBitsSet", "empty"},
//
{intersectInPlaceWrapper, "evenBitsSet", "empty", "empty"},
{intersectInPlaceWrapper, "evenBitsSet", "full", "evenBitsSet"},
{intersectInPlaceWrapper, "evenBitsSet", "firstBitSet", "firstBitSet"},
{intersectInPlaceWrapper, "evenBitsSet", "lastBitSet", "empty"},
{intersectInPlaceWrapper, "evenBitsSet", "lastBitUnset", "evenBitsSet"},
{intersectInPlaceWrapper, "evenBitsSet", "outerBitsSet", "firstBitSet"},
{intersectInPlaceWrapper, "evenBitsSet", "oddBitsSet", "empty"},
{intersectInPlaceWrapper, "evenBitsSet", "evenBitsSet", "evenBitsSet"},
// union
{union, "empty", "empty", "empty"},
{union, "empty", "full", "full"},

View file

@ -481,6 +481,288 @@ func TestBitmap_Intersection(t *testing.T) {
}
func TestBitmap_IntersectionInPlace(t *testing.T) {
bm0 := roaring.NewFileBitmap(0, 2683177)
bm1 := roaring.NewFileBitmap()
for i := uint64(628); i < 2683301; i++ {
if _, err := bm1.Add(i); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != 1 {
t.Fatalf("unexpected n: %d", n)
}
}
func TestBitmap_IntersectionInPlace_Empty(t *testing.T) {
bm0 := roaring.NewFileBitmap(0, 2683177)
bm1 := roaring.NewFileBitmap()
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != 0 {
t.Fatalf("unexpected n: %d", n)
}
bm1.IntersectInPlace(roaring.NewFileBitmap(0, 2683177))
if n := bm1.Count(); n != 0 {
t.Fatalf("unexpected n: %d", n)
}
}
func TestBitmap_IntersectArrayArrayInPlace(t *testing.T) {
bm0 := roaring.NewFileBitmap(0, 1, 7, 9, 11, 2683, 5005)
bm1 := roaring.NewFileBitmap(0, 2683, 2684, 5000)
expected := []uint64{0, 2683}
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != 2 {
t.Fatalf("unexpected n: %d", n)
}
for _, e := range expected {
if !bm0.Contains(e) {
t.Fatalf("missing value %d", e)
}
}
// confirm that it also works going the other way
bm1.IntersectInPlace(bm0)
if n := bm1.Count(); n != 2 {
t.Fatalf("unexpected n: %d", n)
}
for _, e := range expected {
if !bm1.Contains(e) {
t.Fatalf("missing value %d", e)
}
}
}
func TestBitmap_IntersectArrayBitmapInPlace(t *testing.T) {
bm0 := roaring.NewFileBitmap(0, 1, 7, 9, 11, 2683, 5005)
bm1 := roaring.NewFileBitmap()
for i := uint64(1); i < 65536; i += 3 {
if _, err := bm1.Add(i); err != nil {
t.Fatalf("adding bits(%d): %v", i, err)
}
}
expected := []uint64{1, 7, 2683, 5005}
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != uint64(len(expected)) {
t.Fatalf("unexpected n: %d", n)
}
for _, e := range expected {
if !bm0.Contains(e) {
t.Fatalf("missing value %d", e)
}
}
}
func TestBitmap_IntersectArrayRunInPlace(t *testing.T) {
bm0 := roaring.NewFileBitmap(0, 1, 7, 9, 11, 2683, 5005)
bm1 := roaring.NewFileBitmap(5, 6, 7, 8, 9, 10, 11, 13)
bm1.Optimize() // convert to runs
expected := []uint64{7, 9, 11}
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != uint64(len(expected)) {
t.Fatalf("unexpected n: %d", n)
}
for _, e := range expected {
if !bm0.Contains(e) {
t.Fatalf("missing value %d", e)
}
}
}
func TestBitmap_IntersectBitmapBitmapInPlace(t *testing.T) {
bm0 := roaring.NewFileBitmap()
for i := uint64(0); i < 65536; i += 2 {
if _, err := bm0.Add(i); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
bm1 := roaring.NewFileBitmap()
for i := uint64(0); i < 65536; i += 3 {
if _, err := bm1.Add(i); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != 10923 {
t.Fatalf("unexpected n: %d", n)
}
}
func TestBitmap_IntersectBitmapArrayInPlace(t *testing.T) {
bm0 := roaring.NewFileBitmap(0, 1, 7, 9, 11, 2683, 5005)
bm1 := roaring.NewFileBitmap()
for i := uint64(1); i < 65536; i += 3 {
if _, err := bm1.Add(i); err != nil {
t.Fatalf("adding bits(%d): %v", i, err)
}
}
expected := []uint64{1, 7, 2683, 5005}
bm1.IntersectInPlace(bm0)
if n := bm1.Count(); n != uint64(len(expected)) {
t.Fatalf("unexpected n: %d", n)
}
for _, e := range expected {
if !bm1.Contains(e) {
t.Fatalf("missing value %d", e)
}
}
bm11 := roaring.NewFileBitmap(5, 6, 7, 8, 9, 10, 11, 13, 2683)
bm12 := roaring.NewFileBitmap(6, 7, 10, 13, 2683)
bm0.IntersectInPlace(bm11, bm12)
expected = []uint64{7, 2683}
if n := bm0.Count(); n != uint64(len(expected)) {
t.Fatalf("unexpected n: %d", n)
}
for _, e := range expected {
if !bm0.Contains(e) {
t.Fatalf("missing value %d", e)
}
}
}
func TestBitmap_IntersectBitmapRunInPlace(t *testing.T) {
// Intersect two runs that result in a bitmap.
bm1 := roaring.NewFileBitmap()
runLen := uint64(25)
spaceLen := uint64(8)
offset := (runLen / 2) + spaceLen
for i := uint64(0); i < (65536 - runLen - offset); i += (runLen + spaceLen) {
for j := uint64(0); j < runLen; j++ {
if _, err := bm1.Add(offset + i + j); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
}
bm1.Optimize() // convert to runs
bm0 := roaring.NewFileBitmap()
runLen = uint64(32)
spaceLen = uint64(1)
for i := uint64(0); i < (65536 - runLen); i += (runLen + spaceLen) {
for j := uint64(0); j < runLen; j++ {
if _, err := bm0.Add(i + j); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
}
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != 47628 {
t.Fatalf("unexpected n: %d", n)
}
}
func TestBitmap_IntersectRunRunInPlace(t *testing.T) {
// Intersect two runs that result in an array.
bm0 := roaring.NewFileBitmap(0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15)
bm0.Optimize() // convert to runs
bm1 := roaring.NewFileBitmap(5, 6, 7, 8, 9, 10, 11, 13)
bm1.Optimize() // convert to runs
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != 4 {
t.Fatalf("unexpected n: %d", n)
}
// Intersect two runs that result in a bitmap.
bm2 := roaring.NewFileBitmap()
runLen := uint64(25)
spaceLen := uint64(8)
offset := (runLen / 2) + spaceLen
for i := uint64(0); i < (65536 - runLen - offset); i += (runLen + spaceLen) {
for j := uint64(0); j < runLen; j++ {
if _, err := bm2.Add(offset + i + j); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
}
bm2.Optimize() // convert to runs
bm3 := roaring.NewFileBitmap()
runLen = uint64(32)
spaceLen = uint64(1)
for i := uint64(0); i < (65536 - runLen); i += (runLen + spaceLen) {
for j := uint64(0); j < runLen; j++ {
if _, err := bm3.Add(i + j); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
}
bm3.Optimize() // convert to runs
bm2.IntersectInPlace(bm3)
if n := bm2.Count(); n != 47628 {
t.Fatalf("unexpected n: %d", n)
}
}
func TestBitmap_IntersectRunArrayInPlace(t *testing.T) {
// Intersect two runs that result in an array.
bm0 := roaring.NewFileBitmap(0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15)
bm0.Optimize() // convert to runs
bm1 := roaring.NewFileBitmap(5, 6, 7, 8, 9, 10, 11, 13)
bm0.IntersectInPlace(bm1)
expected := []uint64{5, 10, 11, 13}
if n := bm0.Count(); n != uint64(len(expected)) {
t.Fatalf("unexpected n: %d", n)
}
for _, e := range expected {
if !bm0.Contains(e) {
t.Fatalf("missing value %d", e)
}
}
}
func TestBitmap_IntersectRunBitmapInPlace(t *testing.T) {
// Intersect two runs that result in a bitmap.
bm0 := roaring.NewFileBitmap()
runLen := uint64(25)
spaceLen := uint64(8)
offset := (runLen / 2) + spaceLen
for i := uint64(0); i < (65536 - runLen - offset); i += (runLen + spaceLen) {
for j := uint64(0); j < runLen; j++ {
if _, err := bm0.Add(offset + i + j); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
}
bm0.Optimize() // convert to runs
bm1 := roaring.NewFileBitmap()
runLen = uint64(32)
spaceLen = uint64(1)
for i := uint64(0); i < (65536 - runLen); i += (runLen + spaceLen) {
for j := uint64(0); j < runLen; j++ {
if _, err := bm1.Add(i + j); err != nil {
t.Fatalf("adding bits: %v", err)
}
}
}
bm0.IntersectInPlace(bm1)
if n := bm0.Count(); n != 47628 {
t.Fatalf("unexpected n: %d", n)
}
}
func TestBitmap_Union1(t *testing.T) {
bm0 := roaring.NewFileBitmap(0, 2683177)
bm1 := roaring.NewFileBitmap()
@ -1193,9 +1475,13 @@ func testBitmapQuick(t *testing.T, n int, min, max uint64) {
}
}
func TestBitmap_Marshal_Quick_Array1(t *testing.T) { testBitmapMarshalQuick(t, 1000, 1000, 2000, false) }
func TestBitmap_Marshal_Quick_Array2(t *testing.T) { testBitmapMarshalQuick(t, 10000, 0, 1000, false) }
func TestBitmap_Marshal_Quick_Bitmap1(t *testing.T) { testBitmapMarshalQuick(t, 10000, 0, 10000, false) }
func TestBitmap_Marshal_Quick_Array1(t *testing.T) {
testBitmapMarshalQuick(t, 1000, 1000, 2000, false)
}
func TestBitmap_Marshal_Quick_Array2(t *testing.T) { testBitmapMarshalQuick(t, 10000, 0, 1000, false) }
func TestBitmap_Marshal_Quick_Bitmap1(t *testing.T) {
testBitmapMarshalQuick(t, 10000, 0, 10000, false)
}
func TestBitmap_Marshal_Quick_Bitmap2(t *testing.T) {
testBitmapMarshalQuick(t, 10000, 10000, 20000, false)
}
@ -1403,55 +1689,55 @@ func isAllType(b *roaring.Bitmap, typ string) bool {
return true
}
// getBenchData yields some sample data
func getBenchData(tb testing.TB) *benchmarkSampleData {
data := &sampleData
if data.a1 == nil {
// throughout this, we ignore any errors from bitmap adds,
// because errors in those should result in the Optimize
// pass producing the wrong values, so we can just check there.
const max = (1 << 24) / 64
func newBenchData(tb testing.TB) *benchmarkSampleData {
tb.Helper()
// Build bitmap with array container.
data.a1 = roaring.NewFileBitmap()
data.a2 = roaring.NewFileBitmap()
// two lists of different lengths
for i, n := 0, roaring.ArrayMaxSize/3; i < n; i++ {
_, _ = data.a1.Add(uint64(rand.Intn(max)))
_, _ = data.a2.Add(uint64(rand.Intn(max)))
}
for i, n := 0, roaring.ArrayMaxSize/3; i < n; i++ {
_, _ = data.a1.Add(uint64(rand.Intn(max)))
}
var data benchmarkSampleData
// Build bitmap with bitmap container.
data.b = roaring.NewFileBitmap()
for i, n := 0, MaxContainerVal/3; i < n; i++ {
_, _ = data.b.Add(uint64(i * 3))
}
// build bitmap with run container
data.r1 = roaring.NewFileBitmap()
for i, n := 0, MaxContainerVal; i < n; i++ {
_, _ = data.r1.Add(uint64(i))
}
// build bitmap with multiple runs
data.r2 = roaring.NewFileBitmap()
for i, n := 0, MaxContainerVal; i < n; i++ {
_, _ = data.r2.Add(uint64(i))
// break the runs up, this should produce 16 runs, which
// is small enough to make RLE tempting
if i&0xfff == 0xfff {
i += 5
}
}
data.a1.Optimize()
data.a2.Optimize()
data.b.Optimize()
data.r1.Optimize()
data.r2.Optimize()
// throughout this, we ignore any errors from bitmap adds,
// because errors in those should result in the Optimize
// pass producing the wrong values, so we can just check there.
const max = (1 << 24) / 64
// Build bitmap with array container.
data.a1 = roaring.NewFileBitmap()
data.a2 = roaring.NewFileBitmap()
// two lists of different lengths
for i, n := 0, roaring.ArrayMaxSize/3; i < n; i++ {
_, _ = data.a1.Add(uint64(rand.Intn(max)))
_, _ = data.a2.Add(uint64(rand.Intn(max)))
}
for i, n := 0, roaring.ArrayMaxSize/3; i < n; i++ {
_, _ = data.a1.Add(uint64(rand.Intn(max)))
}
// Build bitmap with bitmap container.
data.b = roaring.NewFileBitmap()
for i, n := 0, MaxContainerVal/3; i < n; i++ {
_, _ = data.b.Add(uint64(i * 3))
}
// build bitmap with run container
data.r1 = roaring.NewFileBitmap()
for i, n := 0, MaxContainerVal; i < n; i++ {
_, _ = data.r1.Add(uint64(i))
}
// build bitmap with multiple runs
data.r2 = roaring.NewFileBitmap()
for i, n := 0, MaxContainerVal; i < n; i++ {
_, _ = data.r2.Add(uint64(i))
// break the runs up, this should produce 16 runs, which
// is small enough to make RLE tempting
if i&0xfff == 0xfff {
i += 5
}
}
data.a1.Optimize()
data.a2.Optimize()
data.b.Optimize()
data.r1.Optimize()
data.r2.Optimize()
if !isAllType(data.a1, "array") {
tb.Fatalf("expected data.a1 to be an array, it wasn't.")
}
@ -1467,6 +1753,34 @@ func getBenchData(tb testing.TB) *benchmarkSampleData {
if !isAllType(data.r2, "run") {
tb.Fatalf("expected data.r2 to be RLE, it wasn't.")
}
return &data
}
// getBenchData yields some sample data
func getBenchData(tb testing.TB) *benchmarkSampleData {
tb.Helper()
data := &sampleData
if data.a1 == nil {
data = newBenchData(tb)
} else {
if !isAllType(data.a1, "array") {
tb.Fatalf("expected data.a1 to be an array, it wasn't.")
}
if !isAllType(data.a2, "array") {
tb.Fatalf("expected data.a2 to be an array, it wasn't.")
}
if !isAllType(data.b, "bitmap") {
tb.Fatalf("expected data.b to be a bitmap, it wasn't.")
}
if !isAllType(data.r1, "run") {
tb.Fatalf("expected data.r1 to be RLE, it wasn't.")
}
if !isAllType(data.r2, "run") {
tb.Fatalf("expected data.r2 to be RLE, it wasn't.")
}
}
return data
}
@ -1726,23 +2040,11 @@ func BenchmarkSliceDescendingStriped(b *testing.B) {
}
}
func BenchmarkUnion(b *testing.B) {
data := getBenchData(b)
for n := 0; n < b.N; n++ {
data.a1.
Union(data.a2).
Union(data.b).
Union(data.r1).
Union(data.r2)
}
}
func BenchmarkUnionBulk(b *testing.B) {
data := getBenchData(b)
for n := 0; n < b.N; n++ {
bm := roaring.NewBitmap()
bm.
UnionInPlace(data.a1, data.a2, data.b, data.r1, data.r2)
data := getBenchData(b)
bm := data.a1.Clone()
bm.UnionInPlace(data.a2, data.b, data.r1, data.r2)
}
}
@ -1789,13 +2091,58 @@ func TestBitmap_DifferenceInPlace(t *testing.T) {
}
}
func BenchmarkDifferencInPlace(b *testing.B) {
data := getBenchData(b)
func BenchmarkUnion(b *testing.B) {
for n := 0; n < b.N; n++ {
data := getBenchData(b)
bm := roaring.NewBitmap()
bm.
UnionInPlace(data.a1, data.a2, data.b, data.r1, data.r2)
bm.
DifferenceInPlace(data.a1, data.r2, data.b, data.r1)
bm.Union(data.a1).Union(data.a2).Union(data.b).Union(data.r1).Union(data.r2)
}
}
func BenchmarkUnionInPlace(b *testing.B) {
for n := 0; n < b.N; n++ {
data := getBenchData(b)
bm := data.a1.Clone()
bm.UnionInPlace(data.a2, data.b, data.r1, data.r2)
}
}
func BenchmarkDifference(b *testing.B) {
for n := 0; n < b.N; n++ {
data := getBenchData(b)
bm := data.a1.Clone()
bm.UnionInPlace(data.a2, data.b, data.r1, data.r2)
bm.Difference(data.r2).Difference(data.b).Difference(data.r1)
}
}
func BenchmarkDifferenceInPlace(b *testing.B) {
for n := 0; n < b.N; n++ {
data := getBenchData(b)
bm := data.a1.Clone()
bm.UnionInPlace(data.a2, data.b, data.r1, data.r2)
bm.DifferenceInPlace(data.r2, data.b, data.r1)
}
}
func BenchmarkIntersect(b *testing.B) {
for n := 0; n < b.N; n++ {
data := getBenchData(b)
bm := data.a1.Clone()
bm.UnionInPlace(data.a2, data.b, data.r1, data.r2)
bm.Intersect(data.a2).Intersect(data.b).Intersect(data.r1).Intersect(data.r2)
}
}
func BenchmarkIntersectInPlace(b *testing.B) {
for n := 0; n < b.N; n++ {
data := getBenchData(b)
bm := data.a1.Clone()
bm.UnionInPlace(data.a2, data.b, data.r1, data.r2)
bm.IntersectInPlace(data.a2, data.b, data.r1, data.r2)
}
}