Skip to content

Commit 9e37418

Browse files
authored
Merge pull request #282 from jacksonrnewhouse/array_container_alloc_optimizations
Array container alloc optimizations
2 parents 7521df4 + e8fc4e5 commit 9e37418

File tree

2 files changed

+130
-21
lines changed

2 files changed

+130
-21
lines changed

arraycontainer.go

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -359,35 +359,31 @@ func (ac *arrayContainer) iorArray(value2 *arrayContainer) container {
359359
len1 := value1.getCardinality()
360360
len2 := value2.getCardinality()
361361
maxPossibleCardinality := len1 + len2
362-
if maxPossibleCardinality > arrayDefaultMaxSize { // it could be a bitmap!
363-
bc := newBitmapContainer()
364-
for k := 0; k < len(value2.content); k++ {
365-
v := value2.content[k]
366-
i := uint(v) >> 6
367-
mask := uint64(1) << (v % 64)
368-
bc.bitmap[i] |= mask
369-
}
370-
for k := 0; k < len(ac.content); k++ {
371-
v := ac.content[k]
372-
i := uint(v) >> 6
373-
mask := uint64(1) << (v % 64)
374-
bc.bitmap[i] |= mask
375-
}
376-
bc.cardinality = int(popcntSlice(bc.bitmap))
377-
if bc.cardinality <= arrayDefaultMaxSize {
378-
return bc.toArrayContainer()
379-
}
380-
return bc
381-
}
382362
if maxPossibleCardinality > cap(value1.content) {
383-
newcontent := make([]uint16, 0, maxPossibleCardinality)
363+
// doubling the capacity reduces new slice allocations in the case of
364+
// repeated calls to iorArray().
365+
newSize := 2 * maxPossibleCardinality
366+
// the second check is to handle overly large array containers
367+
// and should not occur in normal usage,
368+
// as all array containers should be at most arrayDefaultMaxSize
369+
if newSize > 8192 && maxPossibleCardinality <= 8192 {
370+
newSize = 8192
371+
}
372+
newcontent := make([]uint16, 0, newSize)
384373
copy(newcontent[len2:maxPossibleCardinality], ac.content[0:len1])
385374
ac.content = newcontent
386375
} else {
387376
copy(ac.content[len2:maxPossibleCardinality], ac.content[0:len1])
388377
}
389378
nl := union2by2(value1.content[len2:maxPossibleCardinality], value2.content, ac.content)
390379
ac.content = ac.content[:nl] // reslice to match actual used capacity
380+
381+
if nl > arrayDefaultMaxSize {
382+
// Only converting to a bitmap when arrayDefaultMaxSize
383+
// is actually exceeded minimizes conversions in the case of repeated
384+
// calls to iorArray().
385+
return ac.toBitmapContainer()
386+
}
391387
return ac
392388
}
393389

roaring_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,3 +2384,116 @@ func TestIterateHalt(t *testing.T) {
23842384
expected = expected[0 : len(expected)-1]
23852385
assert.Equal(t, expected, values)
23862386
}
2387+
2388+
func BenchmarkEvenIntervalArrayUnions(b *testing.B) {
2389+
inputBitmaps := make([]*Bitmap, 40)
2390+
for i := 0; i < 40; i++ {
2391+
bitmap := NewBitmap()
2392+
for j := 0; j < 100; j++ {
2393+
bitmap.Add(uint32(2 * (j + 10*i)))
2394+
}
2395+
inputBitmaps[i] = bitmap
2396+
}
2397+
2398+
b.ResetTimer()
2399+
b.ReportAllocs()
2400+
for i := 0; i < b.N; i++ {
2401+
bitmap := NewBitmap()
2402+
for _, input := range inputBitmaps {
2403+
bitmap.Or(input)
2404+
}
2405+
}
2406+
}
2407+
2408+
func BenchmarkInPlaceArrayUnions(b *testing.B) {
2409+
rand.Seed(100)
2410+
b.ReportAllocs()
2411+
componentBitmaps := make([]*Bitmap, 100)
2412+
for i := 0; i < 100; i++ {
2413+
bitmap := NewBitmap()
2414+
for j := 0; j < 100; j++ {
2415+
//keep all entries in [0,4096), so they stay arrays.
2416+
bitmap.Add(uint32(rand.Intn(arrayDefaultMaxSize)))
2417+
}
2418+
componentBitmaps[i] = bitmap
2419+
}
2420+
b.ResetTimer()
2421+
for i := 0; i < b.N; i++ {
2422+
bitmap := NewBitmap()
2423+
for j := 0; j < 100; j++ {
2424+
bitmap.Or(componentBitmaps[rand.Intn(100)])
2425+
}
2426+
}
2427+
}
2428+
2429+
func BenchmarkAntagonisticArrayUnionsGrowth(b *testing.B) {
2430+
left := NewBitmap()
2431+
right := NewBitmap()
2432+
for i := 0; i < 4096; i++ {
2433+
left.Add(uint32(2 * i))
2434+
right.Add(uint32(2*i + 1))
2435+
}
2436+
b.ReportAllocs()
2437+
b.ResetTimer()
2438+
for i := 0; i < b.N; i++ {
2439+
left.Clone().Or(right)
2440+
}
2441+
}
2442+
2443+
func BenchmarkRepeatedGrowthArrayUnion(b *testing.B) {
2444+
b.ReportAllocs()
2445+
for i := 0; i < b.N; i++ {
2446+
sink := NewBitmap()
2447+
source := NewBitmap()
2448+
for i := 0; i < 2048; i++ {
2449+
source.Add(uint32(2 * i))
2450+
sink.Or(source)
2451+
}
2452+
}
2453+
}
2454+
2455+
func BenchmarkRepeatedSelfArrayUnion(b *testing.B) {
2456+
bitmap := NewBitmap()
2457+
for i := 0; i < 4096; i++ {
2458+
bitmap.Add(uint32(2 * i))
2459+
}
2460+
b.ReportAllocs()
2461+
b.ResetTimer()
2462+
for i := 0; i < b.N; i++ {
2463+
receiver := NewBitmap()
2464+
for j := 0; j < 1000; j++ {
2465+
receiver.Or(bitmap)
2466+
}
2467+
}
2468+
}
2469+
2470+
// BenchmarkArrayIorMergeThreshold tests performance
2471+
// when unioning two array containers when the cardinality sum is over 4096
2472+
func BenchmarkArrayUnionThreshold(b *testing.B) {
2473+
testOddPoint := map[string]int{
2474+
"mostly-overlap": 4900,
2475+
"little-overlap": 2000,
2476+
"no-overlap": 0,
2477+
}
2478+
for name, oddPoint := range testOddPoint {
2479+
b.Run(name, func(b *testing.B) {
2480+
left := NewBitmap()
2481+
right := NewBitmap()
2482+
for i := 0; i < 5000; i++ {
2483+
if i%2 == 0 {
2484+
left.Add(uint32(i))
2485+
}
2486+
if i%2 == 0 && i < oddPoint {
2487+
right.Add(uint32(i))
2488+
} else if i%2 == 1 && i >= oddPoint {
2489+
right.Add(uint32(i))
2490+
}
2491+
}
2492+
b.ResetTimer()
2493+
b.ReportAllocs()
2494+
for i := 0; i < b.N; i++ {
2495+
right.Clone().Or(left)
2496+
}
2497+
})
2498+
}
2499+
}

0 commit comments

Comments
 (0)