Skip to content

Commit

Permalink
Adds bitmap.NextMany() functionality - a higher performance bulk next…
Browse files Browse the repository at this point in the history
… method. +tests +benchmarks
  • Loading branch information
Ben Shaw committed Feb 22, 2018
1 parent 7580aaa commit 92a1592
Show file tree
Hide file tree
Showing 12 changed files with 486 additions and 13 deletions.
12 changes: 6 additions & 6 deletions aggregation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
)

func testAggregations(t *testing.T,
and func(bitmaps ... *Bitmap) *Bitmap,
or func(bitmaps ... *Bitmap) *Bitmap,
xor func(bitmaps ... *Bitmap) *Bitmap) {
and func(bitmaps ...*Bitmap) *Bitmap,
or func(bitmaps ...*Bitmap) *Bitmap,
xor func(bitmaps ...*Bitmap) *Bitmap) {

t.Run("simple case", func(t *testing.T) {
rb1 := NewBitmap()
Expand Down Expand Up @@ -271,10 +271,10 @@ func testAggregations(t *testing.T,
}

func TestParAggregations(t *testing.T) {
andFunc := func(bitmaps ... *Bitmap) *Bitmap {
andFunc := func(bitmaps ...*Bitmap) *Bitmap {
return ParAnd(0, bitmaps...)
}
orFunc := func(bitmaps ... *Bitmap) *Bitmap {
orFunc := func(bitmaps ...*Bitmap) *Bitmap {
return ParOr(0, bitmaps...)
}

Expand All @@ -287,4 +287,4 @@ func TestFastAggregations(t *testing.T) {

func TestHeapAggregations(t *testing.T) {
testAggregations(t, nil, HeapOr, HeapXor)
}
}
4 changes: 4 additions & 0 deletions arraycontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func (ac *arrayContainer) getShortIterator() shortIterable {
return &shortIterator{ac.content, 0}
}

func (ac *arrayContainer) getManyIterator() manyIterable {
return &manyIterator{ac.content, 0}
}

func (ac *arrayContainer) minimum() uint16 {
return ac.content[0] // assume not empty
}
Expand Down
97 changes: 92 additions & 5 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,15 +497,102 @@ func BenchmarkEqualsClone(b *testing.B) {
}
}

func BenchmarkSequentialAdd(b *testing.B) {
for j := 0; j < b.N; j++ {
s := NewBitmap()
for i := 0; i < 10000000; i += 16 {
s.Add(uint32(i))
// go test -bench BenchmarkNexts -benchmem -run -
func BenchmarkNexts(b *testing.B) {

for _, gap := range []uint32{1, 2, 4, 8, 16, 32, 64, 256, 1024, 8096} {

rrs := make([]uint32, 500000)
v := uint32(0)
for i := range rrs {
rrs[i] = v
v += gap
}

bm := NewBitmap()
bm.AddMany(rrs)

var totnext uint64
var totnextmany uint64

density := float32(100) / float32(gap)

density_str := fmt.Sprintf("__%f%%", density)

b.Run("next"+density_str, func(b *testing.B) {
for n := 0; n < b.N; n++ {
totnext = 0
iter := bm.Iterator()
for iter.HasNext() {
v := iter.Next()
totnext += uint64(v)
}
}
b.StopTimer()
})

b.Run("nextmany"+density_str, func(b *testing.B) {
for n := 0; n < b.N; n++ {
totnextmany = 0
iter := bm.ManyIterator()
// worst case, in practice will reuse buffers across many roars
buf := make([]uint32, 4096)
for j := iter.NextMany(buf); j != 0; j = iter.NextMany(buf) {
for i := 0; i < j; i++ {
totnextmany += uint64(buf[i])
}
}
}
b.StopTimer()
})

if totnext != totnextmany {
b.Fatalf("Cardinalities don't match: %d, %d", totnext, totnextmany)
}
}
}

// go test -bench BenchmarkRLENexts -benchmem -run -
func BenchmarkNextsRLE(b *testing.B) {

var totadd uint64
var totaddmany uint64

bm := NewBitmap()
bm.AddRange(0, 1000000)

b.Run("next", func(b *testing.B) {
for n := 0; n < b.N; n++ {
totadd = 0
iter := bm.Iterator()
for iter.HasNext() {
v := iter.Next()
totadd += uint64(v)
}
}
b.StopTimer()
})

b.Run("nextmany", func(b *testing.B) {
for n := 0; n < b.N; n++ {
totaddmany = 0
iter := bm.ManyIterator()
// worst case, in practice will reuse buffers across many roars
buf := make([]uint32, 2048)
for j := iter.NextMany(buf); j != 0; j = iter.NextMany(buf) {
for i := 0; i < j; i++ {
totaddmany += uint64(buf[i])
}
}
}
b.StopTimer()
})
if totadd != totaddmany {
b.Fatalf("Cardinalities don't match: %d, %d", totadd, totaddmany)
}

}

func BenchmarkXor(b *testing.B) {
b.StopTimer()
r := rand.New(rand.NewSource(0))
Expand Down
43 changes: 43 additions & 0 deletions bitmapcontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,56 @@ func (bcsi *bitmapContainerShortIterator) next() uint16 {
func (bcsi *bitmapContainerShortIterator) hasNext() bool {
return bcsi.i >= 0
}

func newBitmapContainerShortIterator(a *bitmapContainer) *bitmapContainerShortIterator {
return &bitmapContainerShortIterator{a, a.NextSetBit(0)}
}

func (bc *bitmapContainer) getShortIterator() shortIterable {
return newBitmapContainerShortIterator(bc)
}

type bitmapContainerManyIterator struct {
ptr *bitmapContainer
base int
bitset uint64
}

func (bcmi *bitmapContainerManyIterator) nextMany(hs uint32, buf []uint32) int {
n := 0
base := bcmi.base
bitset := bcmi.bitset

for n < len(buf) {
if bitset == 0 {
base += 1
if base >= len(bcmi.ptr.bitmap) {
bcmi.base = base
bcmi.bitset = bitset
return n
}
bitset = bcmi.ptr.bitmap[base]
continue
}
t := bitset & -bitset
buf[n] = uint32(((base * 64) + int(popcount(t-1)))) | hs
n = n + 1
bitset ^= t
}

bcmi.base = base
bcmi.bitset = bitset
return n
}

func newBitmapContainerManyIterator(a *bitmapContainer) *bitmapContainerManyIterator {
return &bitmapContainerManyIterator{a, -1, 0}
}

func (bc *bitmapContainer) getManyIterator() manyIterable {
return newBitmapContainerManyIterator(bc)
}

func (bc *bitmapContainer) getSizeInBytes() int {
return len(bc.bitmap) * 8 // + bcBaseBytes
}
Expand Down
23 changes: 23 additions & 0 deletions manyiterator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package roaring

type manyIterable interface {
nextMany(hs uint32, buf []uint32) int
}

type manyIterator struct {
slice []uint16
loc int
}

func (si *manyIterator) nextMany(hs uint32, buf []uint32) int {
n := 0
l := si.loc
s := si.slice
for n < len(buf) && l < len(s) {
buf[n] = uint32(s[l]) | hs
l++
n++
}
si.loc = l
return n
}
29 changes: 29 additions & 0 deletions real_data_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,35 @@ func benchmarkRealDataAggregate(b *testing.B, aggregator func(b []*Bitmap) uint6
}
}

func BenchmarkRealDataNext(b *testing.B) {
benchmarkRealDataAggregate(b, func(bitmaps []*Bitmap) uint64 {
tot := uint64(0)
for _, b := range bitmaps {
it := b.Iterator()
for it.HasNext() {
tot += uint64(it.Next())
}
}
return tot
})
}

func BenchmarkRealDataNextMany(b *testing.B) {
benchmarkRealDataAggregate(b, func(bitmaps []*Bitmap) uint64 {
tot := uint64(0)
buf := make([]uint32, 4096)
for _, b := range bitmaps {
it := b.ManyIterator()
for n := it.NextMany(buf); n != 0; n = it.NextMany(buf) {
for _, v := range buf[:n] {
tot += uint64(v)
}
}
}
return tot
})
}

func BenchmarkRealDataParOr(b *testing.B) {
benchmarkRealDataAggregate(b, func(bitmaps []*Bitmap) uint64 {
return ParOr(0, bitmaps...).GetCardinality()
Expand Down
62 changes: 62 additions & 0 deletions rle16.go
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,68 @@ func (ri *runIterator16) remove() uint16 {
return cur
}

type manyRunIterator16 struct {
rc *runContainer16
curIndex int64
curPosInIndex uint16
curSeq int64
}

func (rc *runContainer16) newManyRunIterator16() *manyRunIterator16 {
return &manyRunIterator16{rc: rc, curIndex: -1}
}

func (ri *manyRunIterator16) hasNext() bool {
if len(ri.rc.iv) == 0 {
return false
}
if ri.curIndex == -1 {
return true
}
return ri.curSeq+1 < ri.rc.cardinality()
}

// hs are the high bits to include to avoid needing to reiterate over the buffer in NextMany
func (ri *manyRunIterator16) nextMany(hs uint32, buf []uint32) int {
n := 0
if !ri.hasNext() {
return n
}
// start and end are inclusive
for n < len(buf) {
if ri.curIndex == -1 || int(ri.rc.iv[ri.curIndex].length-ri.curPosInIndex) <= 0 {
ri.curPosInIndex = 0
ri.curIndex++
if ri.curIndex == int64(len(ri.rc.iv)) {
break
}
buf[n] = uint32(ri.rc.iv[ri.curIndex].start) | hs
if ri.curIndex != 0 {
ri.curSeq += 1
}
n += 1
// not strictly necessarily due to len(buf)-n min check, but saves some work
continue
}
// add as many as you can from this seq
moreVals := min(int(ri.rc.iv[ri.curIndex].length-ri.curPosInIndex), len(buf)-n)

base := uint32(ri.rc.iv[ri.curIndex].start+ri.curPosInIndex+1) | hs

// allows BCE
buf2 := buf[n : n+moreVals]
for i := range buf2 {
buf2[i] = base + uint32(i)
}

// update values
ri.curPosInIndex += uint16(moreVals) //moreVals always fits in uint16
ri.curSeq += int64(moreVals)
n += moreVals
}
return n
}

// remove removes key from the container.
func (rc *runContainer16) removeKey(key uint16) (wasPresent bool) {

Expand Down
Loading

0 comments on commit 92a1592

Please sign in to comment.