Skip to content

Commit 2bf931c

Browse files
authored
Merge pull request #418 from neena/neena/faster-iandnot-bm
faster iandnot between bitmap containers and run containers
2 parents c99a062 + ce0ef6c commit 2bf931c

File tree

3 files changed

+95
-48
lines changed

3 files changed

+95
-48
lines changed

benchmark_test.go

+50-46
Original file line numberDiff line numberDiff line change
@@ -1176,55 +1176,59 @@ func BenchmarkAndNot(b *testing.B) {
11761176
}
11771177

11781178
for _, inPlace := range []bool{true, false} {
1179-
for _, leftGen := range []generator{makeRunContainer, makeArrayContainer, makeBitmapContainer} {
1180-
for _, rightGen := range []generator{makeRunContainer, makeArrayContainer, makeBitmapContainer} {
1181-
b.Run(fmt.Sprintf("inPlace=%v/left=%s/right=%s", inPlace, leftGen.name, rightGen.name), func(b *testing.B) {
1182-
b.StopTimer()
1183-
serializedLefts := make([][]byte, 1000)
1184-
for i := range serializedLefts {
1185-
var err error
1186-
serializedLefts[i], err = leftGen.f().ToBytes()
1187-
if err != nil {
1188-
b.Fatal(err)
1189-
}
1190-
}
1191-
serializedRights := make([][]byte, 1000)
1192-
for i := range serializedRights {
1193-
var err error
1194-
serializedRights[i], err = rightGen.f().ToBytes()
1195-
if err != nil {
1196-
b.Fatal(err)
1197-
}
1198-
}
1179+
b.Run(fmt.Sprintf("inPlace=%v", inPlace), func(b *testing.B) {
1180+
for _, leftGen := range []generator{makeRunContainer, makeArrayContainer, makeBitmapContainer} {
1181+
b.Run(fmt.Sprintf("left=%s", leftGen.name), func(b *testing.B) {
1182+
for _, rightGen := range []generator{makeRunContainer, makeArrayContainer, makeBitmapContainer} {
1183+
b.Run(fmt.Sprintf("right=%s", rightGen.name), func(b *testing.B) {
1184+
b.StopTimer()
1185+
serializedLefts := make([][]byte, 1000)
1186+
for i := range serializedLefts {
1187+
var err error
1188+
serializedLefts[i], err = leftGen.f().ToBytes()
1189+
if err != nil {
1190+
b.Fatal(err)
1191+
}
1192+
}
1193+
serializedRights := make([][]byte, 1000)
1194+
for i := range serializedRights {
1195+
var err error
1196+
serializedRights[i], err = rightGen.f().ToBytes()
1197+
if err != nil {
1198+
b.Fatal(err)
1199+
}
1200+
}
11991201

1200-
lefts := make([]*Bitmap, b.N)
1201-
for i := range lefts {
1202-
buf := serializedLefts[i%len(serializedLefts)]
1203-
lefts[i] = NewBitmap()
1204-
if _, err := lefts[i].FromBuffer(buf); err != nil {
1205-
b.Fatal(err)
1206-
}
1207-
lefts[i] = lefts[i].Clone()
1208-
}
1209-
rights := make([]*Bitmap, b.N)
1210-
for i := range rights {
1211-
buf := serializedRights[i%len(serializedRights)]
1212-
rights[i] = NewBitmap()
1213-
if _, err := rights[i].FromBuffer(buf); err != nil {
1214-
b.Fatal(err)
1215-
}
1216-
rights[i] = rights[i].Clone()
1217-
}
1218-
b.StartTimer()
1219-
for i := 0; i < b.N; i++ {
1220-
if inPlace {
1221-
lefts[i].AndNot(rights[i])
1222-
} else {
1223-
_ = AndNot(lefts[i], rights[i])
1224-
}
1202+
lefts := make([]*Bitmap, b.N)
1203+
for i := range lefts {
1204+
buf := serializedLefts[i%len(serializedLefts)]
1205+
lefts[i] = NewBitmap()
1206+
if _, err := lefts[i].FromBuffer(buf); err != nil {
1207+
b.Fatal(err)
1208+
}
1209+
lefts[i] = lefts[i].Clone()
1210+
}
1211+
rights := make([]*Bitmap, b.N)
1212+
for i := range rights {
1213+
buf := serializedRights[i%len(serializedRights)]
1214+
rights[i] = NewBitmap()
1215+
if _, err := rights[i].FromBuffer(buf); err != nil {
1216+
b.Fatal(err)
1217+
}
1218+
rights[i] = rights[i].Clone()
1219+
}
1220+
b.StartTimer()
1221+
for i := 0; i < b.N; i++ {
1222+
if inPlace {
1223+
lefts[i].AndNot(rights[i])
1224+
} else {
1225+
_ = AndNot(lefts[i], rights[i])
1226+
}
1227+
}
1228+
})
12251229
}
12261230
})
12271231
}
1228-
}
1232+
})
12291233
}
12301234
}

bitmapcontainer.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -893,8 +893,28 @@ func (bc *bitmapContainer) iandNotArray(ac *arrayContainer) container {
893893
}
894894

895895
func (bc *bitmapContainer) iandNotRun16(rc *runContainer16) container {
896-
rcb := rc.toBitmapContainer()
897-
return bc.iandNotBitmapSurely(rcb)
896+
if rc.isEmpty() || bc.isEmpty() {
897+
// Nothing to do.
898+
return bc
899+
}
900+
901+
wordRangeStart := rc.iv[0].start / 64
902+
wordRangeEnd := (rc.iv[len(rc.iv)-1].last()) / 64 // inclusive
903+
904+
cardinalityChange := popcntSlice(bc.bitmap[wordRangeStart : wordRangeEnd+1]) // before cardinality - after cardinality (for word range)
905+
906+
for _, iv := range rc.iv {
907+
resetBitmapRange(bc.bitmap, int(iv.start), int(iv.last())+1)
908+
}
909+
910+
cardinalityChange -= popcntSlice(bc.bitmap[wordRangeStart : wordRangeEnd+1])
911+
912+
bc.cardinality -= int(cardinalityChange)
913+
914+
if bc.getCardinality() <= arrayDefaultMaxSize {
915+
return bc.toArrayContainer()
916+
}
917+
return bc
898918
}
899919

900920
func (bc *bitmapContainer) andNotArray(value2 *arrayContainer) container {

bitmapcontainer_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package roaring
22

33
import (
4+
"math"
45
"math/rand"
56
"testing"
67

78
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
810
)
911

1012
// bitmapContainer's numberOfRuns() function should be correct against the runContainer equivalent
@@ -305,3 +307,24 @@ func TestBitmapContainerResetTo(t *testing.T) {
305307
assert.True(t, dirty.toEfficientContainer().equals(run))
306308
})
307309
}
310+
311+
func TestBitmapContainerIAndNot(t *testing.T) {
312+
var bc container
313+
bc = newBitmapContainer()
314+
for i := 0; i < arrayDefaultMaxSize; i++ {
315+
bc.iadd(uint16(i * 3))
316+
}
317+
bc.iadd(math.MaxUint16)
318+
319+
var rc container
320+
rc = newRunContainer16Range(0, 1)
321+
for i := 0; i < arrayDefaultMaxSize-3; i++ {
322+
rc = rc.iaddRange(i*3, i*3+1)
323+
}
324+
rc.iaddRange(math.MaxUint16-3, math.MaxUint16+1)
325+
326+
bc = bc.iandNot(rc)
327+
328+
require.ElementsMatch(t, []uint16{12279, 12282, 12285}, bc.(*arrayContainer).content)
329+
require.Equal(t, 3, bc.getCardinality())
330+
}

0 commit comments

Comments
 (0)