Skip to content

Commit

Permalink
feature: introduces CloneToBuf method
Browse files Browse the repository at this point in the history
  • Loading branch information
aliszka committed Dec 13, 2024
1 parent 05faef1 commit 03a0842
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 0 deletions.
26 changes: 26 additions & 0 deletions bitmap_opt.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sroar

import (
"fmt"
"sync"
)

Expand Down Expand Up @@ -680,3 +681,28 @@ func (ra *Bitmap) capInBytes() int {
}
return cap(ra.data) * 2
}

func (ra *Bitmap) CloneToBuf(buf []byte) *Bitmap {
bbuf := buf
if l := len(buf); l%2 != 0 {
bbuf = buf[:l-1]
}

src := ra
if ra == nil {
src = NewBitmap()
}

srclen := src.LenInBytes()
if srclen > len(bbuf) {
panic(fmt.Sprintf("Buffer too small, given %d, required %d", len(buf), srclen))
}

srcbuf := toByteSlice(src.data)
copy(bbuf, srcbuf)

// adjust length to src length, keep capacity as entire buffer
bm := FromBuffer(bbuf)
bm.data = bm.data[:srclen/2]
return bm
}
129 changes: 129 additions & 0 deletions bitmap_opt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,135 @@ func TestCapBytes(t *testing.T) {
})
}

func TestCloneToBuf(t *testing.T) {
assertEqualBitmaps := func(t *testing.T, bm, cloned *Bitmap) {
require.Equal(t, bm.GetCardinality(), cloned.GetCardinality())
require.Equal(t, bm.LenInBytes(), cloned.LenInBytes())
require.ElementsMatch(t, bm.ToArray(), cloned.ToArray())
}

t.Run("non-nil bitmap", func(t *testing.T) {
bmEmpty := NewBitmap()

bm1 := NewBitmap()
bm1.Set(1)

bm2 := NewBitmap()
bm2.Set(1)
bm2.Set(1 + uint64(maxCardinality))
bm2.Set(2 + uint64(maxCardinality))

bm3 := NewBitmap()
bm3.Set(1)
bm3.Set(1 + uint64(maxCardinality))
bm3.Set(2 + uint64(maxCardinality))
bm3.Set(1 + uint64(maxCardinality)*2)
bm3.Set(2 + uint64(maxCardinality)*2)
bm3.Set(3 + uint64(maxCardinality)*2)

for name, bm := range map[string]*Bitmap{
"empty": bmEmpty,
"bm1": bm1,
"bm2": bm2,
"bm3": bm3,
} {
t.Run(name, func(t *testing.T) {
t.Run("buf of exact size", func(t *testing.T) {
buf := make([]byte, bm.LenInBytes())
cloned := bm.CloneToBuf(buf)

assertEqualBitmaps(t, bm, cloned)
require.Equal(t, len(buf), cloned.capInBytes())
})
t.Run("buf of bigger size", func(t *testing.T) {
buf := make([]byte, bm.LenInBytes()*3/2)
cloned := bm.CloneToBuf(buf)

assertEqualBitmaps(t, bm, cloned)
require.Equal(t, len(buf), cloned.capInBytes())
})
})
}
})

t.Run("nil bitmap, cloned as empty bitmap", func(t *testing.T) {
var bmNil *Bitmap
bmEmpty := NewBitmap()

buf := make([]byte, bmEmpty.LenInBytes()*2)
cloned := bmNil.CloneToBuf(buf)

assertEqualBitmaps(t, bmEmpty, cloned)
require.Equal(t, len(buf), cloned.capInBytes())
})

t.Run("source bitmap is not changed on cloned updates", func(t *testing.T) {
bm := NewBitmap()
bm.Set(1)
bmLen := bm.LenInBytes()
bmCap := bm.capInBytes()

buf := make([]byte, bm.LenInBytes()*4)
cloned := bm.CloneToBuf(buf)
cloned.Set(1 + uint64(maxCardinality))
cloned.Set(1 + uint64(maxCardinality)*2)

require.Equal(t, bmLen, bm.LenInBytes())
require.Equal(t, bmCap, bm.capInBytes())
require.Equal(t, 1, bm.GetCardinality())
require.ElementsMatch(t, []uint64{1}, bm.ToArray())

require.Less(t, bmLen, cloned.LenInBytes())
require.LessOrEqual(t, bmCap, cloned.capInBytes())
require.Equal(t, 3, cloned.GetCardinality())
require.Equal(t, []uint64{1, 1 + uint64(maxCardinality), 1 + uint64(maxCardinality)*2}, cloned.ToArray())
})

t.Run("reuse bigger buffer to expand size", func(t *testing.T) {
bm := NewBitmap()
bm.Set(1)

// buf big enough for additional containers
buf := make([]byte, bm.LenInBytes()*4)
cloned := bm.CloneToBuf(buf)
clonedLen := cloned.LenInBytes()
clonedCap := cloned.capInBytes()

cloned.Set(1 + uint64(maxCardinality))
cloned.Set(1 + uint64(maxCardinality)*2)

require.Less(t, clonedLen, cloned.LenInBytes())
require.Equal(t, clonedCap, cloned.capInBytes())
})

t.Run("panic on smaller buffer size", func(t *testing.T) {
defer func() {
r := recover()
require.NotNil(t, r)
require.Contains(t, r, "Buffer too small")
}()

bm := NewBitmap()
bm.Set(1)
bmLen := bm.LenInBytes()

buf := make([]byte, bmLen-1)
bm.CloneToBuf(buf)
})

t.Run("allow buffer of odd size", func(t *testing.T) {
bm := NewBitmap()
bm.Set(1)
bmLen := bm.LenInBytes()

buf := make([]byte, bmLen+3)
cloned := bm.CloneToBuf(buf)

require.Equal(t, bmLen, cloned.LenInBytes())
require.Equal(t, bmLen+2, cloned.capInBytes())
})
}

func TestMergeToSuperset(t *testing.T) {
run := func(t *testing.T, bufs [][]uint16) {
containerThreshold := uint64(math.MaxUint16 + 1)
Expand Down

0 comments on commit 03a0842

Please sign in to comment.