@@ -3,6 +3,7 @@ package roaring
3
3
import (
4
4
"bytes"
5
5
"fmt"
6
+ "github.com/stretchr/testify/require"
6
7
"math/rand"
7
8
"runtime"
8
9
"testing"
@@ -235,6 +236,75 @@ func BenchmarkUnionRoaring(b *testing.B) {
235
236
}
236
237
}
237
238
239
+ // BenchmarkUnionInPlaceCopyOnWrite tests the performance of bitmap.Or()
240
+ // when the bitmap was generated via FromBuffer.
241
+ // In this case all left containers need to be copied in order to be updated.
242
+ // The nested for-loops test a number of different scenarios
243
+ // with respect to the ranges and densities of bitmaps.
244
+ func BenchmarkUnionInPlaceCopyOnWrite (b * testing.B ) {
245
+ //uint32s to maintain 1.12 compatibility, which requires unsigned shifts.
246
+ startingContainerPower := uint32 (4 )
247
+ finalContainerPower := uint32 (10 )
248
+ containerIncrement := uint32 (3 )
249
+ startingItemsPower := uint32 (3 )
250
+ finalItemsPower := uint32 (10 )
251
+ itemsIncrement := uint32 (7 )
252
+ for leftContainerPower := startingContainerPower ; leftContainerPower <= finalContainerPower ; leftContainerPower += containerIncrement {
253
+ for rightContainerPower := startingContainerPower ; rightContainerPower <= finalContainerPower ; rightContainerPower += containerIncrement {
254
+ for leftItemsPerContainerPower := startingItemsPower ; leftItemsPerContainerPower <= finalItemsPower ; leftItemsPerContainerPower += itemsIncrement {
255
+ for rightItemsPerContainerPower := startingItemsPower ; rightItemsPerContainerPower <= finalItemsPower ; rightItemsPerContainerPower += itemsIncrement {
256
+ b .Run (fmt .Sprintf ("%d-%d-%d-%d" , leftContainerPower , rightContainerPower , leftItemsPerContainerPower , rightItemsPerContainerPower ),
257
+ func (b * testing.B ) {
258
+ leftMax := (1 << 16 ) << leftContainerPower
259
+ rightMax := (1 << 16 ) << rightContainerPower
260
+ leftItems := 1 << (leftContainerPower + leftItemsPerContainerPower )
261
+ rightItems := 1 << (rightContainerPower + rightItemsPerContainerPower )
262
+ left := make ([][]byte , 10 )
263
+ right := make ([]* Bitmap , 10 )
264
+ for i := 0 ; i < 10 ; i ++ {
265
+ right [i ] = NewBitmap ()
266
+ left [i ] = generateRandomBitmap (b , leftMax , leftItems )
267
+ _ , err := right [i ].FromBuffer (generateRandomBitmap (b , rightMax , rightItems ))
268
+ require .NoError (b , err )
269
+ }
270
+ // This tests a destructive operation, Or() so have to have a fresh bitmap per test.
271
+ targetLefts := make ([]* Bitmap , b .N )
272
+ for i := 0 ; i < b .N ; i ++ {
273
+ targetLefts [i ] = NewBitmap ()
274
+ _ , err := targetLefts [i ].FromBuffer (left [i % 10 ])
275
+ require .NoError (b , err )
276
+ }
277
+ runActualBenchmark (b , targetLefts , right )
278
+ })
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+
285
+ // runActualBenchmark is broken out primarily so you can profile the tests,
286
+ // as otherwise the generation overwhelms the actual test.
287
+ func runActualBenchmark (b * testing.B , targetLefts []* Bitmap , right []* Bitmap ) uint64 {
288
+ b .ResetTimer ()
289
+ b .ReportAllocs ()
290
+ total := uint64 (0 )
291
+ for i := 0 ; i < b .N ; i ++ {
292
+ targetLefts [i ].Or (right [i % 10 ])
293
+ total += targetLefts [i ].GetCardinality ()
294
+ }
295
+ return total
296
+ }
297
+
298
+ func generateRandomBitmap (b * testing.B , max , terms int ) []byte {
299
+ bitmap := NewBitmap ()
300
+ for i := 0 ; i < terms ; i ++ {
301
+ bitmap .Add (uint32 (rand .Intn (max )))
302
+ }
303
+ result , err := bitmap .ToBytes ()
304
+ require .NoError (b , err )
305
+ return result
306
+ }
307
+
238
308
// go test -bench BenchmarkSize -run -
239
309
func BenchmarkSizeBitset (b * testing.B ) {
240
310
b .StopTimer ()
0 commit comments