diff --git a/roaring64/BSI_BENCHMARKS.md b/roaring64/BSI_BENCHMARKS.md new file mode 100644 index 00000000..506a37f2 --- /dev/null +++ b/roaring64/BSI_BENCHMARKS.md @@ -0,0 +1,44 @@ +# BSI64 Benchmarks + +These notes capture local benchmark results for the BSI64 `BatchEqual` and +comparison paths. They are intended as reproducible PR evidence, not as +contractual performance guarantees. + +Environment: + +- CPU: 12th Gen Intel(R) Core(TM) i7-1255U +- OS/arch: linux/amd64 +- Package: `github.com/RoaringBitmap/roaring/v2/roaring64` + +Commands: + +```sh +go test ./roaring64 -count=1 +go test ./roaring64 -run '^$' -bench 'BenchmarkBSI64BatchEqual' -benchmem -count 3 +go test ./roaring64 -run '^$' -bench 'BenchmarkBSI64Compare(Big)?Value|BenchmarkBSI64BatchEqual(Big)?LargeAgeFixture' -benchmem -count 1 +``` + +Representative results: + +| Benchmark | Before | After | Notes | +| --- | ---: | ---: | --- | +| `BenchmarkBSI64BatchEqualLargeAgeFixture` | ~13-14s/op, ~12.4GB/op | ~145-205ms/op, ~25.5MB/op | Avoids row-by-row `GetBigValue` for int64-width values. | +| `BenchmarkBSI64BatchEqualM128Scattered` | ~1.25s/op, ~458MB/op | ~11-17ms/op, ~12.5MB/op | Detects complete bit-cube value patterns. | +| `BenchmarkBSI64CompareValueEQLargeAgeFixture` | ~4.44s/op, ~461MB/op | ~100-118ms/op, ~19.7MB/op | `EQ` delegates to optimized `BatchEqual`. | +| `BenchmarkBSI64CompareValueRangeLargeAgeFixture` | ~7.49s/op, ~501MB/op | ~204-224ms/op, ~122.6MB/op | Uses bitmap-native signed int64 comparison. | +| `BenchmarkBSI64CompareValueGELargeAgeFixture` | ~3.45s/op, ~500MB/op | ~168-184ms/op, ~82.3MB/op | Uses bitmap-native signed int64 comparison. | + +Compatibility: + +- Public method signatures are unchanged. +- `CompareBigValue` and `BatchEqualBig` internally delegate to the optimized + int64 paths only when the BSI and query values fit in signed 64-bit space. +- True wider-than-64-bit values continue to use the existing generic paths. +- `BatchEqualBig` now keys values by sign and magnitude so positive and negative + values with the same magnitude do not collide. + +Follow-up: + +- This change is scoped to `roaring64`. The 32-bit `BitSliceIndexing` package + already has separate `BatchEqual` coverage, and `CompareValue` parity can be + addressed in a follow-up PR with its own benchmarks and signed-value tests. diff --git a/roaring64/bsi64.go b/roaring64/bsi64.go index e9b1eca4..a5428aef 100644 --- a/roaring64/bsi64.go +++ b/roaring64/bsi64.go @@ -5,6 +5,7 @@ import ( "io" "math/big" "runtime" + "sort" "sync" ) @@ -127,6 +128,7 @@ func (b *BSI) SetBigValue(columnID uint64, value *big.Int) { b.eBM.Add(columnID) } +// SetBigMany sets value for all columns in foundSet. func (b *BSI) SetBigMany(foundSet *Bitmap, value *big.Int) { // If max/min values are set to zero then automatically determine bit array size if b.MaxValue == 0 && b.MinValue == 0 { @@ -348,9 +350,96 @@ type task struct { func (b *BSI) CompareValue(parallelism int, op Operation, valueOrStart, end int64, foundSet *Bitmap) *Bitmap { + if result, ok := b.compareInt64Value(parallelism, op, valueOrStart, end, foundSet); ok { + return result + } return b.CompareBigValue(parallelism, op, big.NewInt(valueOrStart), big.NewInt(end), foundSet) } +func (b *BSI) compareInt64Value(parallelism int, op Operation, valueOrStart, end int64, foundSet *Bitmap) (*Bitmap, bool) { + bitCount := b.BitCount() + if bitCount > 63 || !bsi64ValueFitsBitCount(valueOrStart, bitCount) { + return nil, false + } + if op == EQ { + result := b.BatchEqual(parallelism, []int64{valueOrStart}) + if foundSet != nil { + result.And(foundSet) + } + return result, true + } + if op == RANGE && !bsi64ValueFitsBitCount(end, bitCount) { + return nil, false + } + + universe := b.eBM.Clone() + if foundSet != nil { + universe.And(foundSet) + } + if universe.IsEmpty() { + return universe, true + } + + start := transformBSI64SignedEncoding(encodeBSI64Value(valueOrStart, bitCount), bitCount) + less, equal := b.compareInt64LessAndEqual(start, universe) + + switch op { + case LT: + return less, true + case LE: + less.Or(equal) + return less, true + case GE: + universe.AndNot(less) + return universe, true + case GT: + less.Or(equal) + universe.AndNot(less) + return universe, true + case RANGE: + if valueOrStart > end { + return NewBitmap(), true + } + universe.AndNot(less) + finish := transformBSI64SignedEncoding(encodeBSI64Value(end, bitCount), bitCount) + rangeLess, rangeEqual := b.compareInt64LessAndEqual(finish, universe) + rangeLess.Or(rangeEqual) + return rangeLess, true + default: + return nil, false + } +} + +func transformBSI64SignedEncoding(encoded uint64, bitCount int) uint64 { + return encoded ^ (uint64(1) << uint(bitCount)) +} + +func (b *BSI) compareInt64LessAndEqual(target uint64, universe *Bitmap) (*Bitmap, *Bitmap) { + less := NewBitmap() + equalPrefix := universe.Clone() + for i := b.BitCount(); i >= 0; i-- { + targetBitSet := target&(uint64(1)<= 64 { + // Fall back to the arbitrary-precision path when the BSI has more than + // int64's finite bit width. This preserves correctness for big-value BSIs. + bigValues := make([]*big.Int, len(values)) + for i, v := range values { + bigValues[i] = big.NewInt(v) + } + return b.BatchEqualBig(parallelism, bigValues) + } + + seen := make(map[uint64]struct{}, len(values)) + vals := make([]uint64, 0, len(values)) + for _, v := range values { + if !bsi64ValueFitsBitCount(v, bitCount) { + continue + } + encoded := encodeBSI64Value(v, bitCount) + if _, ok := seen[encoded]; ok { + continue + } + seen[encoded] = struct{}{} + vals = append(vals, encoded) + } + if len(vals) == 0 { + return NewBitmap() + } + + sort.Slice(vals, func(i, j int) bool { return vals[i] < vals[j] }) + if result, ok := b.matchInt64Cube(vals, bitCount); ok { + if b.runOptimized { + result.RunOptimize() + } + return result + } + result := b.matchInt64Trie(vals, bitCount, &b.eBM, false) + if b.runOptimized { + result.RunOptimize() + } + return result +} + +func bsi64ValueFitsBitCount(value int64, bitCount int) bool { + if bitCount >= 63 { + return true + } + min := -(int64(1) << uint(bitCount)) + max := (int64(1) << uint(bitCount)) - 1 + return value >= min && value <= max +} + +func encodeBSI64Value(value int64, bitCount int) uint64 { + if bitCount >= 63 { + return uint64(value) + } + mask := (uint64(1) << uint(bitCount+1)) - 1 + return uint64(value) & mask +} + +func (b *BSI) matchInt64Cube(vals []uint64, bitCount int) (*Bitmap, bool) { + if bitCount >= 63 { + return nil, false + } + widthMask := (uint64(1) << uint(bitCount+1)) - 1 + fixedOnes := vals[0] & widthMask + fixedZeros := ^vals[0] & widthMask + for _, v := range vals[1:] { + fixedOnes &= v + fixedZeros &= ^v & widthMask + } + + variableMask := ^(fixedOnes | fixedZeros) & widthMask + combinations := uint64(1) << uint(countBSI64Bits(variableMask)) + if uint64(len(vals)) != combinations { + return nil, false + } + for _, v := range vals { + if v&fixedOnes != fixedOnes || (^v)&fixedZeros != fixedZeros { + return nil, false + } + } + + result := b.eBM.Clone() + for i := 0; i <= bitCount; i++ { + bit := uint64(1) << uint(i) + if variableMask&bit != 0 { + continue + } + if fixedOnes&bit != 0 { + result.And(&b.bA[i]) + } else { + result.AndNot(&b.bA[i]) + } + if result.IsEmpty() { + break + } + } + return result, true +} + +func countBSI64Bits(value uint64) int { + count := 0 + for value != 0 { + value &= value - 1 + count++ } - return b.BatchEqualBig(parallelism, bigValues) + return count +} + +func (b *BSI) matchInt64Trie(vals []uint64, p int, prefix *Bitmap, owned bool) *Bitmap { + if prefix.IsEmpty() { + if owned { + return prefix + } + return NewBitmap() + } + if p < 0 || (p < 63 && uint64(len(vals)) == uint64(1)< 63 { + return nil, false + } + intValues := make([]int64, 0, len(values)) + for _, value := range values { + if value == nil { + continue + } + if !value.IsInt64() { + return nil, false + } + intValues = append(intValues, value.Int64()) + } + if len(intValues) == 0 { + return nil, false + } + return intValues, true +} + func batchEqual(e *task, batch []uint64, resultsChan chan *Bitmap, wg *sync.WaitGroup) { @@ -1002,7 +1300,7 @@ func batchEqual(e *task, batch []uint64, resultsChan chan *Bitmap, for i := 0; i < len(batch); i++ { cID := batch[i] if value, ok := e.bsi.GetBigValue(cID); ok { - if _, yes := e.values[string(value.Bytes())]; yes { + if _, yes := e.values[batchEqualBigKey(value)]; yes { results.Add(cID) } } diff --git a/roaring64/bsi64_batch_equal_test.go b/roaring64/bsi64_batch_equal_test.go new file mode 100644 index 00000000..73716daa --- /dev/null +++ b/roaring64/bsi64_batch_equal_test.go @@ -0,0 +1,228 @@ +package roaring64 + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +func expectedBSI64BatchEqual(bsi *BSI, query []int64) *Bitmap { + expected := NewBitmap() + want := make(map[int64]struct{}, len(query)) + for _, q := range query { + want[q] = struct{}{} + } + iter := bsi.GetExistenceBitmap().Iterator() + for iter.HasNext() { + col := iter.Next() + val, ok := bsi.GetValue(col) + if ok { + if _, hit := want[val]; hit { + expected.Add(col) + } + } + } + return expected +} + +func TestBSI64BatchEqualEdgeCases(t *testing.T) { + bsi := NewDefaultBSI() + res := bsi.BatchEqual(0, nil) + assert.True(t, res.IsEmpty()) + + res = bsi.BatchEqual(0, []int64{}) + assert.True(t, res.IsEmpty()) + + bsi.SetValue(10, 42) + bsi.SetValue(20, 100) + bsi.SetValue(30, 42) + bsi.SetValue(40, -5) + bsi.SetValue(50, 5) + + res = bsi.BatchEqual(0, []int64{42}) + assert.Equal(t, uint64(2), res.GetCardinality()) + assert.True(t, res.Contains(10)) + assert.True(t, res.Contains(30)) + + res = bsi.BatchEqual(0, []int64{42, 100, 42, 999}) + assert.Equal(t, uint64(3), res.GetCardinality()) + assert.True(t, res.Contains(10)) + assert.True(t, res.Contains(20)) + assert.True(t, res.Contains(30)) + + res = bsi.BatchEqual(0, []int64{-5}) + assert.Equal(t, uint64(1), res.GetCardinality()) + assert.True(t, res.Contains(40)) + assert.False(t, res.Contains(50), "negative and positive values with the same magnitude must not collide") + + res = bsi.BatchEqual(0, []int64{5}) + assert.Equal(t, uint64(1), res.GetCardinality()) + assert.True(t, res.Contains(50)) + assert.False(t, res.Contains(40), "positive and negative values with the same magnitude must not collide") + + bsi62 := NewBSI(1<<62, 0) + bsi62.SetValue(10, 5) + res = bsi62.BatchEqual(0, []int64{5}) + assert.Equal(t, uint64(1), res.GetCardinality()) + assert.True(t, res.Contains(10)) +} + +func TestBSI64BatchEqualSubBitWidthMatchesGetValue(t *testing.T) { + bsi := NewBSI(100, 0) + assert.Equal(t, 7, bsi.BitCount()) + + bsi.SetValue(10, 42) + bsi.SetValue(20, 99) + + for _, query := range [][]int64{{-5}, {200}, {-5, 42, 200}} { + expected := expectedBSI64BatchEqual(bsi, query) + actual := bsi.BatchEqual(0, query) + assert.True(t, actual.Equals(expected), "query %v expected %v got %v", query, expected.ToArray(), actual.ToArray()) + } +} + +func TestBSI64BatchEqualResultIsolation(t *testing.T) { + bsi := NewDefaultBSI() + bsi.SetValue(10, 42) + bsi.SetValue(20, 100) + + res := bsi.BatchEqual(0, []int64{42}) + assert.True(t, res.Contains(10)) + + res.Add(999) + res.Remove(10) + + assert.False(t, bsi.GetExistenceBitmap().Contains(999)) + assert.True(t, bsi.GetExistenceBitmap().Contains(10)) + + val, ok := bsi.GetValue(10) + assert.True(t, ok) + assert.Equal(t, int64(42), val) + + _, ok = bsi.GetValue(999) + assert.False(t, ok) +} + +func TestBSI64BatchEqualConsistentWithGetValue(t *testing.T) { + rg := rand.New(rand.NewSource(42)) + for run := 0; run < 15; run++ { + bsi := NewDefaultBSI() + numCols := rg.Intn(1000) + 10 + for col := 0; col < numCols; col++ { + if rg.Float64() < 0.8 { + val := rg.Int63n(500) - 250 + bsi.SetValue(uint64(col), val) + } + } + + querySizes := []int{rg.Intn(10) + 1, rg.Intn(50) + 50, rg.Intn(200) + 100} + for _, querySize := range querySizes { + query := make([]int64, querySize) + for i := range query { + query[i] = rg.Int63n(600) - 300 + } + expected := expectedBSI64BatchEqual(bsi, query) + + for _, parallelism := range []int{0, 1, 2, 4} { + actual := bsi.BatchEqual(parallelism, query) + if !actual.Equals(expected) { + t.Fatalf("run=%d querySize=%d parallelism=%d query=%v expected=%v actual=%v", + run, querySize, parallelism, query, expected.ToArray(), actual.ToArray()) + } + } + } + } +} + +func TestBSI64BatchEqualBitCubePattern(t *testing.T) { + bsi := NewDefaultBSI() + for col := uint64(0); col < 512; col++ { + bsi.SetValue(col, int64(col%256)) + } + + odds := make([]int64, 0, 128) + for v := int64(1); v < 256; v += 2 { + odds = append(odds, v) + } + + expected := expectedBSI64BatchEqual(bsi, odds) + actual := bsi.BatchEqual(0, odds) + assert.True(t, actual.Equals(expected), "expected %v got %v", expected.ToArray(), actual.ToArray()) +} + +func TestBSI64BatchEqualExistenceAuthority(t *testing.T) { + ebm := BitmapOf(1) + plane := BitmapOf(1, 2) + ebmData, err := ebm.MarshalBinary() + if err != nil { + t.Fatal(err) + } + planeData, err := plane.MarshalBinary() + if err != nil { + t.Fatal(err) + } + bsi := NewDefaultBSI() + if err := bsi.UnmarshalBinary([][]byte{ebmData, planeData}); err != nil { + t.Fatal(err) + } + res := bsi.BatchEqual(0, []int64{1}) + assert.True(t, res.Contains(1)) + assert.False(t, res.Contains(2), "column 2 is not in eBM and must not match") + + large := setupLargeBSI(t) + if large == nil { + t.Skip("skipping, large BSI setup failed") + } + for _, vals := range [][]int64{{16}, {55, 57}, {0, 1, 2, 3}} { + res := large.BatchEqual(0, vals) + outside := AndNot(res, large.GetExistenceBitmap()) + assert.True(t, outside.IsEmpty(), "BatchEqual(%v) returned %d columns outside eBM", vals, outside.GetCardinality()) + } +} + +func BenchmarkBSI64BatchEqualLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.BatchEqual(0, []int64{55, 57}) + _ = res + } +} + +func BenchmarkBSI64BatchEqualBigLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + values := []*big.Int{big.NewInt(55), big.NewInt(57)} + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.BatchEqualBig(0, values) + _ = res + } +} + +func BenchmarkBSI64BatchEqualM128(b *testing.B) { benchmarkBSI64BatchEqualM(b, 128, 1) } +func BenchmarkBSI64BatchEqualM128Scattered(b *testing.B) { benchmarkBSI64BatchEqualM(b, 128, 2) } +func BenchmarkBSI64BatchEqualM200(b *testing.B) { benchmarkBSI64BatchEqualM(b, 200, 1) } + +func benchmarkBSI64BatchEqualM(b *testing.B, m int, stride int64) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + vals := make([]int64, m) + for i := range vals { + vals[i] = int64(i)*stride + stride - 1 + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.BatchEqual(0, vals) + _ = res + } +} diff --git a/roaring64/bsi64_compare_benchmark_test.go b/roaring64/bsi64_compare_benchmark_test.go new file mode 100644 index 00000000..7fa0ce47 --- /dev/null +++ b/roaring64/bsi64_compare_benchmark_test.go @@ -0,0 +1,304 @@ +package roaring64 + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +func expectedBSI64CompareValue(bsi *BSI, op Operation, valueOrStart, end int64, foundSet *Bitmap) *Bitmap { + expected := NewBitmap() + source := bsi.GetExistenceBitmap() + if foundSet != nil { + source = And(source, foundSet) + } + iter := source.Iterator() + for iter.HasNext() { + col := iter.Next() + val, ok := bsi.GetValue(col) + if !ok { + continue + } + switch op { + case LT: + if val < valueOrStart { + expected.Add(col) + } + case LE: + if val <= valueOrStart { + expected.Add(col) + } + case EQ: + if val == valueOrStart { + expected.Add(col) + } + case GE: + if val >= valueOrStart { + expected.Add(col) + } + case GT: + if val > valueOrStart { + expected.Add(col) + } + case RANGE: + if val >= valueOrStart && val <= end { + expected.Add(col) + } + default: + panic("unsupported test operation") + } + } + return expected +} + +func TestBSI64CompareValueConsistentWithGetValue(t *testing.T) { + rg := rand.New(rand.NewSource(84)) + for run := 0; run < 15; run++ { + bsi := NewDefaultBSI() + numCols := rg.Intn(1000) + 10 + for col := 0; col < numCols; col++ { + if rg.Float64() < 0.8 { + bsi.SetValue(uint64(col), rg.Int63n(500)-250) + } + } + + foundSet := NewBitmap() + iter := bsi.GetExistenceBitmap().Iterator() + for iter.HasNext() { + col := iter.Next() + if col%3 != 0 { + foundSet.Add(col) + } + } + + cases := []struct { + op Operation + start int64 + end int64 + }{ + {LT, -17, 0}, + {LE, -17, 0}, + {EQ, -17, 0}, + {GE, -17, 0}, + {GT, -17, 0}, + {RANGE, -25, 25}, + } + for _, tc := range cases { + for _, fs := range []*Bitmap{nil, foundSet} { + expected := expectedBSI64CompareValue(bsi, tc.op, tc.start, tc.end, fs) + actual := bsi.CompareValue(0, tc.op, tc.start, tc.end, fs) + assert.True(t, actual.Equals(expected), "run=%d op=%d foundSet=%v expected=%v actual=%v", + run, tc.op, fs != nil, expected.ToArray(), actual.ToArray()) + } + } + } +} + +func TestBSI64CompareBigValueConsistentWithGetBigValue(t *testing.T) { + rg := rand.New(rand.NewSource(85)) + for run := 0; run < 15; run++ { + bsi := NewDefaultBSI() + numCols := rg.Intn(1000) + 10 + for col := 0; col < numCols; col++ { + if rg.Float64() < 0.8 { + bsi.SetValue(uint64(col), rg.Int63n(500)-250) + } + } + + foundSet := NewBitmap() + iter := bsi.GetExistenceBitmap().Iterator() + for iter.HasNext() { + col := iter.Next() + if col%3 != 0 { + foundSet.Add(col) + } + } + + cases := []struct { + op Operation + start int64 + end int64 + }{ + {LT, -17, 0}, + {LE, -17, 0}, + {EQ, -17, 0}, + {GE, -17, 0}, + {GT, -17, 0}, + {RANGE, -25, 25}, + } + for _, tc := range cases { + for _, fs := range []*Bitmap{nil, foundSet} { + expected := expectedBSI64CompareBigValue(bsi, tc.op, big.NewInt(tc.start), big.NewInt(tc.end), fs) + actual := bsi.CompareBigValue(0, tc.op, big.NewInt(tc.start), big.NewInt(tc.end), fs) + assert.True(t, actual.Equals(expected), "run=%d op=%d foundSet=%v expected=%v actual=%v", + run, tc.op, fs != nil, expected.ToArray(), actual.ToArray()) + } + } + } +} + +func expectedBSI64CompareBigValue(bsi *BSI, op Operation, valueOrStart, end *big.Int, foundSet *Bitmap) *Bitmap { + expected := NewBitmap() + source := bsi.GetExistenceBitmap() + if foundSet != nil { + source = And(source, foundSet) + } + iter := source.Iterator() + for iter.HasNext() { + col := iter.Next() + val, ok := bsi.GetBigValue(col) + if !ok { + continue + } + switch op { + case LT: + if val.Cmp(valueOrStart) < 0 { + expected.Add(col) + } + case LE: + if val.Cmp(valueOrStart) <= 0 { + expected.Add(col) + } + case EQ: + if val.Cmp(valueOrStart) == 0 { + expected.Add(col) + } + case GE: + if val.Cmp(valueOrStart) >= 0 { + expected.Add(col) + } + case GT: + if val.Cmp(valueOrStart) > 0 { + expected.Add(col) + } + case RANGE: + if val.Cmp(valueOrStart) >= 0 && val.Cmp(end) <= 0 { + expected.Add(col) + } + default: + panic("unsupported test operation") + } + } + return expected +} + +func TestBSI64CompareBigValueFallsBackForBigWidth(t *testing.T) { + bsi := NewDefaultBSI() + base := new(big.Int).Lsh(big.NewInt(1), 80) + below := new(big.Int).Sub(base, big.NewInt(1)) + above := new(big.Int).Add(base, big.NewInt(1)) + bsi.SetBigValue(1, below) + bsi.SetBigValue(2, base) + bsi.SetBigValue(3, above) + + eq := bsi.CompareBigValue(0, EQ, base, nil, nil) + assert.True(t, eq.Equals(BitmapOf(2))) + + rng := bsi.CompareBigValue(0, RANGE, base, above, nil) + assert.True(t, rng.Equals(BitmapOf(2, 3))) +} + +func BenchmarkBSI64CompareValueEQLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareValue(0, EQ, 55, 0, nil) + _ = res + } +} + +func BenchmarkBSI64CompareBigValueEQLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + value := big.NewInt(55) + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareBigValue(0, EQ, value, nil, nil) + _ = res + } +} + +func BenchmarkBSI64CompareValueEQFoundSetLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + foundSet := bsi.CompareValue(0, RANGE, 40, 70, nil) + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareValue(0, EQ, 55, 0, foundSet) + _ = res + } +} + +func BenchmarkBSI64CompareBigValueEQFoundSetLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + foundSet := bsi.CompareBigValue(0, RANGE, big.NewInt(40), big.NewInt(70), nil) + value := big.NewInt(55) + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareBigValue(0, EQ, value, nil, foundSet) + _ = res + } +} + +func BenchmarkBSI64CompareValueRangeLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareValue(0, RANGE, 40, 70, nil) + _ = res + } +} + +func BenchmarkBSI64CompareBigValueRangeLargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + start := big.NewInt(40) + end := big.NewInt(70) + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareBigValue(0, RANGE, start, end, nil) + _ = res + } +} + +func BenchmarkBSI64CompareValueGELargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareValue(0, GE, 55, 0, nil) + _ = res + } +} + +func BenchmarkBSI64CompareBigValueGELargeAgeFixture(b *testing.B) { + bsi := setupLargeBSI(b) + if bsi == nil { + b.Skip("skipping, large BSI setup failed") + } + value := big.NewInt(55) + b.ResetTimer() + for i := 0; i < b.N; i++ { + res := bsi.CompareBigValue(0, GE, value, nil, nil) + _ = res + } +} diff --git a/roaring64/roaring64.go b/roaring64/roaring64.go index adb508fe..143209de 100644 --- a/roaring64/roaring64.go +++ b/roaring64/roaring64.go @@ -1241,6 +1241,7 @@ func (rb *Bitmap) GetSerializedSizeInBytes() uint64 { return rb.highlowcontainer.serializedSizeInBytes() } +// Validate checks whether the bitmap's internal containers are coherent. func (rb *Bitmap) Validate() error { return rb.highlowcontainer.validate() } diff --git a/roaring64/roaringarray64.go b/roaring64/roaringarray64.go index 09c366ff..f974f27d 100644 --- a/roaring64/roaringarray64.go +++ b/roaring64/roaringarray64.go @@ -14,7 +14,9 @@ type roaringArray64 struct { } var ( - ErrKeySortOrder = errors.New("keys were out of order") + // ErrKeySortOrder reports that container keys are out of order. + ErrKeySortOrder = errors.New("keys were out of order") + // ErrCardinalityConstraint reports inconsistent array cardinality metadata. ErrCardinalityConstraint = errors.New("size of arrays was not coherent") )