Skip to content

Commit

Permalink
Merge pull request #282 from jacksonrnewhouse/array_container_alloc_o…
Browse files Browse the repository at this point in the history
…ptimizations

Array container alloc optimizations
  • Loading branch information
lemire authored Oct 21, 2020
2 parents 7521df4 + e8fc4e5 commit 9e37418
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 21 deletions.
38 changes: 17 additions & 21 deletions arraycontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,35 +359,31 @@ func (ac *arrayContainer) iorArray(value2 *arrayContainer) container {
len1 := value1.getCardinality()
len2 := value2.getCardinality()
maxPossibleCardinality := len1 + len2
if maxPossibleCardinality > arrayDefaultMaxSize { // it could be a bitmap!
bc := newBitmapContainer()
for k := 0; k < len(value2.content); k++ {
v := value2.content[k]
i := uint(v) >> 6
mask := uint64(1) << (v % 64)
bc.bitmap[i] |= mask
}
for k := 0; k < len(ac.content); k++ {
v := ac.content[k]
i := uint(v) >> 6
mask := uint64(1) << (v % 64)
bc.bitmap[i] |= mask
}
bc.cardinality = int(popcntSlice(bc.bitmap))
if bc.cardinality <= arrayDefaultMaxSize {
return bc.toArrayContainer()
}
return bc
}
if maxPossibleCardinality > cap(value1.content) {
newcontent := make([]uint16, 0, maxPossibleCardinality)
// doubling the capacity reduces new slice allocations in the case of
// repeated calls to iorArray().
newSize := 2 * maxPossibleCardinality
// the second check is to handle overly large array containers
// and should not occur in normal usage,
// as all array containers should be at most arrayDefaultMaxSize
if newSize > 8192 && maxPossibleCardinality <= 8192 {
newSize = 8192
}
newcontent := make([]uint16, 0, newSize)
copy(newcontent[len2:maxPossibleCardinality], ac.content[0:len1])
ac.content = newcontent
} else {
copy(ac.content[len2:maxPossibleCardinality], ac.content[0:len1])
}
nl := union2by2(value1.content[len2:maxPossibleCardinality], value2.content, ac.content)
ac.content = ac.content[:nl] // reslice to match actual used capacity

if nl > arrayDefaultMaxSize {
// Only converting to a bitmap when arrayDefaultMaxSize
// is actually exceeded minimizes conversions in the case of repeated
// calls to iorArray().
return ac.toBitmapContainer()
}
return ac
}

Expand Down
113 changes: 113 additions & 0 deletions roaring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2384,3 +2384,116 @@ func TestIterateHalt(t *testing.T) {
expected = expected[0 : len(expected)-1]
assert.Equal(t, expected, values)
}

func BenchmarkEvenIntervalArrayUnions(b *testing.B) {
inputBitmaps := make([]*Bitmap, 40)
for i := 0; i < 40; i++ {
bitmap := NewBitmap()
for j := 0; j < 100; j++ {
bitmap.Add(uint32(2 * (j + 10*i)))
}
inputBitmaps[i] = bitmap
}

b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
bitmap := NewBitmap()
for _, input := range inputBitmaps {
bitmap.Or(input)
}
}
}

func BenchmarkInPlaceArrayUnions(b *testing.B) {
rand.Seed(100)
b.ReportAllocs()
componentBitmaps := make([]*Bitmap, 100)
for i := 0; i < 100; i++ {
bitmap := NewBitmap()
for j := 0; j < 100; j++ {
//keep all entries in [0,4096), so they stay arrays.
bitmap.Add(uint32(rand.Intn(arrayDefaultMaxSize)))
}
componentBitmaps[i] = bitmap
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
bitmap := NewBitmap()
for j := 0; j < 100; j++ {
bitmap.Or(componentBitmaps[rand.Intn(100)])
}
}
}

func BenchmarkAntagonisticArrayUnionsGrowth(b *testing.B) {
left := NewBitmap()
right := NewBitmap()
for i := 0; i < 4096; i++ {
left.Add(uint32(2 * i))
right.Add(uint32(2*i + 1))
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
left.Clone().Or(right)
}
}

func BenchmarkRepeatedGrowthArrayUnion(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
sink := NewBitmap()
source := NewBitmap()
for i := 0; i < 2048; i++ {
source.Add(uint32(2 * i))
sink.Or(source)
}
}
}

func BenchmarkRepeatedSelfArrayUnion(b *testing.B) {
bitmap := NewBitmap()
for i := 0; i < 4096; i++ {
bitmap.Add(uint32(2 * i))
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
receiver := NewBitmap()
for j := 0; j < 1000; j++ {
receiver.Or(bitmap)
}
}
}

// BenchmarkArrayIorMergeThreshold tests performance
// when unioning two array containers when the cardinality sum is over 4096
func BenchmarkArrayUnionThreshold(b *testing.B) {
testOddPoint := map[string]int{
"mostly-overlap": 4900,
"little-overlap": 2000,
"no-overlap": 0,
}
for name, oddPoint := range testOddPoint {
b.Run(name, func(b *testing.B) {
left := NewBitmap()
right := NewBitmap()
for i := 0; i < 5000; i++ {
if i%2 == 0 {
left.Add(uint32(i))
}
if i%2 == 0 && i < oddPoint {
right.Add(uint32(i))
} else if i%2 == 1 && i >= oddPoint {
right.Add(uint32(i))
}
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
right.Clone().Or(left)
}
})
}
}

0 comments on commit 9e37418

Please sign in to comment.