From 054cb206d5ec7f85609d4b609b4dd616304ea06a Mon Sep 17 00:00:00 2001 From: Seebs Date: Tue, 12 Mar 2019 15:51:22 -0500 Subject: [PATCH] improve union-related benchmarking Add a benchmark to test a specific case where UnionInPlace is underperforming the naive union operation badly. Also, the UnionBulk test was reusing a bitmap, meaning that it ended up doing a lot of unions into a bitmap that already had all the bits it was supposed to have. This broke a couple of other tests in unexpected ways. We also now use UnionInPlace in importRoaring, and test it in the container combinations tests via a wrapper. --- fragment.go | 7 +- roaring/roaring_internal_test.go | 165 +++++++++++++++++++++++++++++++ roaring/roaring_test.go | 14 +-- 3 files changed, 177 insertions(+), 9 deletions(-) diff --git a/fragment.go b/fragment.go index 32b870fb9..0801c8001 100644 --- a/fragment.go +++ b/fragment.go @@ -1763,8 +1763,11 @@ func (f *fragment) importRoaring(data []byte, clear bool) error { if clear { bm = f.storage.Difference(bm) - } else if f.storage.Any() { - bm = f.storage.Union(bm) + } else if f.storage.Containers.Size() >= bm.Containers.Size() { + f.storage.UnionInPlace(bm) + bm = f.storage + } else { + bm.UnionInPlace(f.storage) } for rowID := range rowSet { diff --git a/roaring/roaring_internal_test.go b/roaring/roaring_internal_test.go index fb7d54987..927d7c4b2 100644 --- a/roaring/roaring_internal_test.go +++ b/roaring/roaring_internal_test.go @@ -2699,6 +2699,18 @@ func getFunctionName(i interface{}) string { return y[0] } +// UnionInPlace is defined at the Bitmap level, but this wrapper lets us insert +// it into our ContainerCombinations tests so that it gets exercised on a wide +// variety of container data. +func unionInPlaceWrapper(a, b *Container) *Container { + out := NewBitmap() + out.Containers.Put(0, a.Clone()) + B := NewBitmap() + B.Containers.Put(0, b) + out.UnionInPlace(B) + return out.Containers.Get(0) +} + func TestContainerCombinations(t *testing.T) { cts := setupContainerTests() @@ -2935,6 +2947,117 @@ func TestContainerCombinations(t *testing.T) { {union, "evenBitsSet", "oddBitsSet", "full"}, {union, "evenBitsSet", "evenBitsSet", "evenBitsSet"}, + // unionInPlaceWrapper + {unionInPlaceWrapper, "empty", "empty", "empty"}, + {unionInPlaceWrapper, "empty", "full", "full"}, + {unionInPlaceWrapper, "empty", "firstBitSet", "firstBitSet"}, + {unionInPlaceWrapper, "empty", "lastBitSet", "lastBitSet"}, + {unionInPlaceWrapper, "empty", "firstBitUnset", "firstBitUnset"}, + {unionInPlaceWrapper, "empty", "lastBitUnset", "lastBitUnset"}, + {unionInPlaceWrapper, "empty", "innerBitsSet", "innerBitsSet"}, + {unionInPlaceWrapper, "empty", "outerBitsSet", "outerBitsSet"}, + {unionInPlaceWrapper, "empty", "oddBitsSet", "oddBitsSet"}, + {unionInPlaceWrapper, "empty", "evenBitsSet", "evenBitsSet"}, + // + {unionInPlaceWrapper, "full", "empty", "full"}, + {unionInPlaceWrapper, "full", "full", "full"}, + {unionInPlaceWrapper, "full", "firstBitSet", "full"}, + {unionInPlaceWrapper, "full", "lastBitSet", "full"}, + {unionInPlaceWrapper, "full", "firstBitUnset", "full"}, + {unionInPlaceWrapper, "full", "lastBitUnset", "full"}, + {unionInPlaceWrapper, "full", "innerBitsSet", "full"}, + {unionInPlaceWrapper, "full", "outerBitsSet", "full"}, + {unionInPlaceWrapper, "full", "oddBitsSet", "full"}, + {unionInPlaceWrapper, "full", "evenBitsSet", "full"}, + // + {unionInPlaceWrapper, "firstBitSet", "empty", "firstBitSet"}, + {unionInPlaceWrapper, "firstBitSet", "full", "full"}, + {unionInPlaceWrapper, "firstBitSet", "firstBitSet", "firstBitSet"}, + {unionInPlaceWrapper, "firstBitSet", "lastBitSet", "outerBitsSet"}, + {unionInPlaceWrapper, "firstBitSet", "firstBitUnset", "full"}, + {unionInPlaceWrapper, "firstBitSet", "lastBitUnset", "lastBitUnset"}, + {unionInPlaceWrapper, "firstBitSet", "innerBitsSet", "lastBitUnset"}, + {unionInPlaceWrapper, "firstBitSet", "outerBitsSet", "outerBitsSet"}, + //{unionInPlaceWrapper, "firstBitSet", "oddBitsSet", ""}, + {unionInPlaceWrapper, "firstBitSet", "evenBitsSet", "evenBitsSet"}, + // + {unionInPlaceWrapper, "lastBitSet", "empty", "lastBitSet"}, + {unionInPlaceWrapper, "lastBitSet", "full", "full"}, + {unionInPlaceWrapper, "lastBitSet", "firstBitSet", "outerBitsSet"}, + {unionInPlaceWrapper, "lastBitSet", "lastBitSet", "lastBitSet"}, + {unionInPlaceWrapper, "lastBitSet", "firstBitUnset", "firstBitUnset"}, + {unionInPlaceWrapper, "lastBitSet", "lastBitUnset", "full"}, + {unionInPlaceWrapper, "lastBitSet", "innerBitsSet", "firstBitUnset"}, + {unionInPlaceWrapper, "lastBitSet", "outerBitsSet", "outerBitsSet"}, + {unionInPlaceWrapper, "lastBitSet", "oddBitsSet", "oddBitsSet"}, + //{unionInPlaceWrapper, "lastBitSet", "evenBitsSet", ""}, + // + {unionInPlaceWrapper, "firstBitUnset", "empty", "firstBitUnset"}, + {unionInPlaceWrapper, "firstBitUnset", "full", "full"}, + {unionInPlaceWrapper, "firstBitUnset", "firstBitSet", "full"}, + {unionInPlaceWrapper, "firstBitUnset", "lastBitSet", "firstBitUnset"}, + {unionInPlaceWrapper, "firstBitUnset", "firstBitUnset", "firstBitUnset"}, + {unionInPlaceWrapper, "firstBitUnset", "lastBitUnset", "full"}, + {unionInPlaceWrapper, "firstBitUnset", "innerBitsSet", "firstBitUnset"}, + {unionInPlaceWrapper, "firstBitUnset", "outerBitsSet", "full"}, + {unionInPlaceWrapper, "firstBitUnset", "oddBitsSet", "firstBitUnset"}, + {unionInPlaceWrapper, "firstBitUnset", "evenBitsSet", "full"}, + // + {unionInPlaceWrapper, "lastBitUnset", "empty", "lastBitUnset"}, + {unionInPlaceWrapper, "lastBitUnset", "full", "full"}, + {unionInPlaceWrapper, "lastBitUnset", "firstBitSet", "lastBitUnset"}, + {unionInPlaceWrapper, "lastBitUnset", "lastBitSet", "full"}, + {unionInPlaceWrapper, "lastBitUnset", "firstBitUnset", "full"}, + {unionInPlaceWrapper, "lastBitUnset", "lastBitUnset", "lastBitUnset"}, + {unionInPlaceWrapper, "lastBitUnset", "innerBitsSet", "lastBitUnset"}, + {unionInPlaceWrapper, "lastBitUnset", "outerBitsSet", "full"}, + {unionInPlaceWrapper, "lastBitUnset", "oddBitsSet", "full"}, + {unionInPlaceWrapper, "lastBitUnset", "evenBitsSet", "lastBitUnset"}, + // + {unionInPlaceWrapper, "innerBitsSet", "empty", "innerBitsSet"}, + {unionInPlaceWrapper, "innerBitsSet", "full", "full"}, + {unionInPlaceWrapper, "innerBitsSet", "firstBitSet", "lastBitUnset"}, + {unionInPlaceWrapper, "innerBitsSet", "lastBitSet", "firstBitUnset"}, + {unionInPlaceWrapper, "innerBitsSet", "firstBitUnset", "firstBitUnset"}, + {unionInPlaceWrapper, "innerBitsSet", "lastBitUnset", "lastBitUnset"}, + {unionInPlaceWrapper, "innerBitsSet", "innerBitsSet", "innerBitsSet"}, + {unionInPlaceWrapper, "innerBitsSet", "outerBitsSet", "full"}, + {unionInPlaceWrapper, "innerBitsSet", "oddBitsSet", "firstBitUnset"}, + {unionInPlaceWrapper, "innerBitsSet", "evenBitsSet", "lastBitUnset"}, + // + {unionInPlaceWrapper, "outerBitsSet", "empty", "outerBitsSet"}, + {unionInPlaceWrapper, "outerBitsSet", "full", "full"}, + {unionInPlaceWrapper, "outerBitsSet", "firstBitSet", "outerBitsSet"}, + {unionInPlaceWrapper, "outerBitsSet", "lastBitSet", "outerBitsSet"}, + {unionInPlaceWrapper, "outerBitsSet", "firstBitUnset", "full"}, + {unionInPlaceWrapper, "outerBitsSet", "lastBitUnset", "full"}, + {unionInPlaceWrapper, "outerBitsSet", "innerBitsSet", "full"}, + {unionInPlaceWrapper, "outerBitsSet", "outerBitsSet", "outerBitsSet"}, + //{unionInPlaceWrapper, "outerBitsSet", "oddBitsSet", ""}, + //{unionInPlaceWrapper, "outerBitsSet", "evenBitsSet", ""}, + // + {unionInPlaceWrapper, "oddBitsSet", "empty", "oddBitsSet"}, + {unionInPlaceWrapper, "oddBitsSet", "full", "full"}, + //{unionInPlaceWrapper, "oddBitsSet", "firstBitSet", ""}, + {unionInPlaceWrapper, "oddBitsSet", "lastBitSet", "oddBitsSet"}, + {unionInPlaceWrapper, "oddBitsSet", "firstBitUnset", "firstBitUnset"}, + {unionInPlaceWrapper, "oddBitsSet", "lastBitUnset", "full"}, + {unionInPlaceWrapper, "oddBitsSet", "innerBitsSet", "firstBitUnset"}, + //{unionInPlaceWrapper, "oddBitsSet", "outerBitsSet", ""}, + {unionInPlaceWrapper, "oddBitsSet", "oddBitsSet", "oddBitsSet"}, + {unionInPlaceWrapper, "oddBitsSet", "evenBitsSet", "full"}, + // + {unionInPlaceWrapper, "evenBitsSet", "empty", "evenBitsSet"}, + {unionInPlaceWrapper, "evenBitsSet", "full", "full"}, + {unionInPlaceWrapper, "evenBitsSet", "firstBitSet", "evenBitsSet"}, + //{unionInPlaceWrapper, "evenBitsSet", "lastBitSet", ""}, + {unionInPlaceWrapper, "evenBitsSet", "firstBitUnset", "full"}, + {unionInPlaceWrapper, "evenBitsSet", "lastBitUnset", "lastBitUnset"}, + {unionInPlaceWrapper, "evenBitsSet", "innerBitsSet", "lastBitUnset"}, + //{unionInPlaceWrapper, "evenBitsSet", "outerBitsSet", ""}, + {unionInPlaceWrapper, "evenBitsSet", "oddBitsSet", "full"}, + {unionInPlaceWrapper, "evenBitsSet", "evenBitsSet", "evenBitsSet"}, + // difference {difference, "empty", "empty", "empty"}, {difference, "empty", "full", "empty"}, @@ -3653,3 +3776,45 @@ func TestDirectAddNVsAdd(t *testing.T) { } } + +func BenchmarkUnionInPlaceRegression(b *testing.B) { + initial := make([]uint64, 0, 10100) + a1 := make([]uint64, 0, 10000) + a2 := make([]uint64, 0, 10000) + for i := uint64(0); i < 1<<30; i += 100000 { + initial = append(initial, i) + a1 = append(a1, i+67000) + a2 = append(a2, i/2) + } + a1BM := NewBTreeBitmap(a1...) + a2BM := NewBTreeBitmap(a2...) + b.Run("Union1", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bm := NewBTreeBitmap(initial...) + _ = bm.Union(a1BM) + } + }) + b.Run("UnionInPlace1", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bm := NewBTreeBitmap(initial...) + bm.UnionInPlace(a1BM) + } + }) + + b.Run("Union2", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bm := NewBTreeBitmap(initial...) + _ = bm.Union(a1BM, a2BM) + } + }) + b.Run("UnionInPlace2", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bm := NewBTreeBitmap(initial...) + bm.UnionInPlace(a1BM, a2BM) + } + }) +} diff --git a/roaring/roaring_test.go b/roaring/roaring_test.go index acccbf69a..d39103483 100644 --- a/roaring/roaring_test.go +++ b/roaring/roaring_test.go @@ -1275,7 +1275,7 @@ func isAllType(b *roaring.Bitmap, typ string) bool { return true } -func getBenchData(b *testing.B) *benchmarkSampleData { +func getBenchData(tb testing.TB) *benchmarkSampleData { data := &sampleData if data.a1 == nil { const max = (1 << 24) / 64 @@ -1321,19 +1321,19 @@ func getBenchData(b *testing.B) *benchmarkSampleData { } if !isAllType(data.a1, "array") { - b.Fatalf("expected data.a1 to be an array, it wasn't.") + tb.Fatalf("expected data.a1 to be an array, it wasn't.") } if !isAllType(data.a2, "array") { - b.Fatalf("expected data.a2 to be an array, it wasn't.") + tb.Fatalf("expected data.a2 to be an array, it wasn't.") } if !isAllType(data.b, "bitmap") { - b.Fatalf("expected data.b to be a bitmap, it wasn't.") + tb.Fatalf("expected data.b to be a bitmap, it wasn't.") } if !isAllType(data.r1, "run") { - b.Fatalf("expected data.r1 to be RLE, it wasn't.") + tb.Fatalf("expected data.r1 to be RLE, it wasn't.") } if !isAllType(data.r2, "run") { - b.Fatalf("expected data.r2 to be RLE, it wasn't.") + tb.Fatalf("expected data.r2 to be RLE, it wasn't.") } return data } @@ -1607,8 +1607,8 @@ func BenchmarkUnion(b *testing.B) { func BenchmarkUnionBulk(b *testing.B) { data := getBenchData(b) - bm := roaring.NewBitmap() for n := 0; n < b.N; n++ { + bm := roaring.NewBitmap() bm. UnionInPlace(data.a1, data.a2, data.b, data.r1, data.r2) }