From d8c947c2fb5b7e75a6859cbb07ea7da2d16444f5 Mon Sep 17 00:00:00 2001 From: Andrzej Liszka Date: Fri, 6 Dec 2024 11:15:49 +0100 Subject: [PATCH] feature: introduces FillUp method --- benchmark_opt_test.go | 38 ++ bitmap_opt.go | 323 ++++++++++--- bitmap_opt_test.go | 1041 +++++++++++++++++++++++++++++++++++++++++ keys.go | 15 + 4 files changed, 1359 insertions(+), 58 deletions(-) diff --git a/benchmark_opt_test.go b/benchmark_opt_test.go index 9135b57..928c8af 100644 --- a/benchmark_opt_test.go +++ b/benchmark_opt_test.go @@ -32,6 +32,44 @@ func BenchmarkPrefillFromSortedList(b *testing.B) { } } +// go test -v -bench BenchmarkFillUpNative -benchmem -run ^$ github.com/weaviate/sroar -cpuprofile cpu.prof +func BenchmarkFillUpNative(b *testing.B) { + for i := 0; i < b.N; i++ { + bm := Prefill(100_000_000) + bm.FillUp(150_000_000) + bm.FillUp(200_000_000) + } +} + +// go test -v -bench BenchmarkPrefillFromSortedList -benchmem -run ^$ github.com/weaviate/sroar -cpuprofile cpu.prof +func BenchmarkFillUpFromSortedList(b *testing.B) { + prefillBufferSize := 65_536 + prefillX := uint64(100_000_000) + fillupX1 := uint64(150_000_000) + fillupX2 := uint64(200_000_000) + inc := uint64(prefillBufferSize) + buf := make([]uint64, prefillBufferSize) + + for i := 0; i < b.N; i++ { + bm := Prefill(prefillX) + + for i := prefillX + 1; i <= fillupX1; i += inc { + j := uint64(0) + for ; j < inc && i+j <= fillupX1; j++ { + buf[j] = i + j + } + bm.Or(FromSortedList(buf[:j])) + } + for i := fillupX1 + 1; i <= fillupX2; i += inc { + j := uint64(0) + for ; j < inc && i+j <= fillupX2; j++ { + buf[j] = i + j + } + bm.Or(FromSortedList(buf[:j])) + } + } +} + // ================================================================================ // // BENCHMARKS comparing performance of different merge implementations diff --git a/bitmap_opt.go b/bitmap_opt.go index d225248..64b4b96 100644 --- a/bitmap_opt.go +++ b/bitmap_opt.go @@ -710,84 +710,291 @@ func (ra *Bitmap) CloneToBuf(buf []byte) *Bitmap { // Prefill creates bitmap prefilled with elements [0-maxX] func Prefill(maxX uint64) *Bitmap { - maxCard64 := uint64(maxCardinality) + containersCount, remainingCount := calcFullContainersAndRemainingCounts(maxX) - // maxX should be included, therefore +1 - n := maxX / maxCard64 - rem := maxX % maxCard64 - if rem == maxCard64-1 { - n++ - } - rem = (rem + 1) % maxCard64 - - // create additional container for remaining values (or empty if there are not) + // create additional container for remaining values + // (or reserve space for new one if there are not any remaining) // +1 additional key to avoid keys expanding (there should always be 1 spare) - bm := newBitmapWith(int(n)+1+1, maxContainerSize, int(n)*maxContainerSize) + bm := newBitmapWith(int(containersCount)+1+1, maxContainerSize, int(containersCount)*maxContainerSize) + bm.prefill(containersCount, remainingCount) + return bm +} - var refContainer []uint16 - var remOffset = bm.keys.val(0) +func (ra *Bitmap) FillUp(maxX uint64) { + if ra == nil { + return + } - if n > 0 { - refContainer = bm.getContainer(remOffset) - refContainer[indexSize] = maxContainerSize - refContainer[indexType] = typeBitmap - setCardinality(refContainer, maxCardinality) + maxContainersCount, maxRemainingCount := calcFullContainersAndRemainingCounts(maxX) + if ra.IsEmpty() { + // try to fit data into existing memory, + // if there is not enough space anyway, allocate more memory to fit additional container + minimalContainersCount := maxContainersCount + if maxRemainingCount > 0 { + minimalContainersCount++ + } + minimalKeysLen := calcInitialKeysLen(minimalContainersCount + 1) + minimalLen := minimalKeysLen + minimalContainersCount*maxContainerSize - // fill entire bitmap container with ones - refContainer64 := uint16To64SliceUnsafe(refContainer[startIdx:]) - for i := range refContainer64 { - refContainer64[i] = math.MaxUint64 + var bm *Bitmap + if minimalLen <= cap(ra.data) { + bm = newBitampToBuf(minimalKeysLen, maxContainerSize, ra.data) + } else { + bm = newBitmapWith(int(maxContainersCount)+1+1, maxContainerSize, int(maxContainersCount)*maxContainerSize) } + bm.prefill(maxContainersCount, maxRemainingCount) + ra.data = bm.data + ra._ptr = bm._ptr + ra.keys = bm.keys + return + } - // fill remaining containers by copying reference one - for i := uint64(1); i < n; i++ { - key := (i * maxCard64) & mask - offset := bm.newContainerNoClr(maxContainerSize) - bm.setKey(key, offset) + minX := ra.Maximum() + if minX >= maxX { + return + } - copy(bm.data[offset:], refContainer) + maxKey := maxX & mask + minKey := minX & mask + maxY := int(uint16(maxX)) + minY := int(uint16(minX)) + + idx := ra.keys.searchReverse(minKey) + minOffset := ra.keys.val(idx) + + // same container + if maxKey == minKey { + commonContainer := ra.getContainer(minOffset) + card := getCardinality(commonContainer) + newYs := maxY - minY + + switch commonContainer[indexType] { + case typeBitmap: + // set bits in bitmap + bitmap(commonContainer).setRange(minY, maxY, nil) + setCardinality(commonContainer, card+newYs) + case typeArray: + size := commonContainer[indexSize] + if spaceLeft := int(size-startIdx) - card; spaceLeft >= newYs { + // add elements to existing array + for i := 0; i < newYs; i++ { + commonContainer[startIdx+uint16(card+i)] = uint16(minY + 1 + i) + } + setCardinality(commonContainer, card+newYs) + } else { + // create new bitmap container, copy elements from array, set new bits + prevContainer := commonContainer + minOffset = ra.newContainer(maxContainerSize) + ra.setKey(minKey, minOffset) + + commonContainer = ra.fillUpBitmapContainerRange(minOffset, minY, maxY, card+newYs, nil) + for i := 0; i < card; i++ { + y := prevContainer[startIdx+uint16(i)] + commonContainer[startIdx+y/16] |= bitmapMask[y%16] + } + } + default: + panic("unknown container type") } + return + } - // create container for remaining values - key := (n * maxCard64) & mask - remOffset = bm.newContainer(maxContainerSize) - bm.setKey(key, remOffset) + minContainersCount, minRemainingCount := calcFullContainersAndRemainingCounts(minX) + requiredContainersCount := maxContainersCount - minContainersCount + if maxRemainingCount > 0 { + requiredContainersCount++ } - container := bm.getContainer(remOffset) - container[indexSize] = maxContainerSize - container[indexType] = typeBitmap - setCardinality(container, int(rem)) + // first count how many new containers will be required, to allocate memory once, then do the fillup + var fillUpCommonContainer func(commonContainer []uint16, onesBitmap bitmap) = nil + // idx of first full container to add + containerIdx := minContainersCount + if minRemainingCount > 0 { + containerIdx++ + requiredContainersCount-- - if rem > 0 { - n16 := uint16(rem) / 16 - rem16 := uint16(rem) % 16 + commonContainer := ra.getContainer(minOffset) + card := getCardinality(commonContainer) + newYs := maxCardinality - 1 - minY - if refContainer != nil { - // refContainer available (maxX >= math.MaxUint16-1), - // fill remaining values container by copying biggest possible slice of refContainer (batches of 16s) - copy(bm.data[remOffset+uint64(startIdx):], refContainer[startIdx:startIdx+n16]) - // set remaining bits - for i := uint16(0); i < rem16; i++ { - container[startIdx+n16] |= bitmapMask[i] + switch commonContainer[indexType] { + case typeBitmap: + // if bitmap, set proper bits up to maxCardinality + fillUpCommonContainer = func(commonContainer []uint16, onesBitmap bitmap) { + bitmap(commonContainer).setRange(minY, maxCardinality-1, onesBitmap) + setCardinality(commonContainer, card+newYs) } + case typeArray: + size := commonContainer[indexSize] + if spaceLeft := int(size-startIdx) - card; spaceLeft >= newYs { + // if array add new elements if there is enough space left + fillUpCommonContainer = func(commonContainer []uint16, onesBitmap bitmap) { + for i := 0; i < newYs; i++ { + commonContainer[startIdx+uint16(card+i)] = uint16(minY + 1 + i) + } + setCardinality(commonContainer, card+newYs) + } + } else { + // if not enough space, create new bitmap container, set new bits and set old ones + requiredContainersCount++ + fillUpCommonContainer = func(commonContainer []uint16, onesBitmap bitmap) { + prevContainer := commonContainer + offset := ra.newContainer(maxContainerSize) + ra.setKey(minKey, offset) + + commonContainer = ra.fillUpBitmapContainerRange(offset, minY, maxCardinality-1, card+newYs, onesBitmap) + for i := 0; i < card; i++ { + y := prevContainer[startIdx+uint16(i)] + commonContainer[startIdx+y/16] |= bitmapMask[y%16] + } + } + } + default: + panic("unknown container type") + } + } + + // calculate required memory to allocate and expand underlying slice + containersLen := uint64(requiredContainersCount * maxContainerSize) + keysLen := uint64(requiredContainersCount * 2 * 4) + ra.expandNoLengthChange(containersLen + keysLen) + ra.expandKeys(keysLen) + + var onesBitmap bitmap + if containerIdx < maxContainersCount { + // fillup full containers + key := uint64(containerIdx*maxCardinality) & mask + offset := ra.newContainerNoClr(maxContainerSize) + ra.setKey(key, offset) + + onesBitmap = ra.fillUpBitmapContainers(offset, containerIdx+1, maxContainersCount) + } + if maxRemainingCount > 0 { + // fillup last container + key := uint64(maxContainersCount*maxCardinality) & mask + offset := ra.newContainer(maxContainerSize) + ra.setKey(key, offset) + + ra.fillUpBitmapContainerRange(offset, 0, maxRemainingCount-1, maxRemainingCount, onesBitmap) + } + if minRemainingCount > 0 { + // fillup common container using previously created callback + // due to slice expanding, container has to be fetched once again due to new offset + minOffset = ra.keys.val(idx) + commonContainer := ra.getContainer(minOffset) + fillUpCommonContainer(commonContainer, onesBitmap) + } +} + +func (ra *Bitmap) prefill(containersCount, remainingCount int) { + var onesBitmap bitmap + + if containersCount > 0 { + offset := ra.keys.val(0) + onesBitmap = ra.fillUpBitmapContainers(offset, 1, containersCount) + } + if remainingCount > 0 { + var offset uint64 + if containersCount > 0 { + // create container for remaining values + key := uint64(containersCount*maxCardinality) & mask + offset = ra.newContainer(maxContainerSize) + ra.setKey(key, offset) } else { - // refContainer not available (maxX < math.MaxUint16-1), - // set bits by copying MaxUint64 first, then MaxUint16, then single bits - n64 := uint16(rem) / 64 + // get initial container + offset = ra.keys.val(0) + } + ra.fillUpBitmapContainerRange(offset, 0, remainingCount-1, remainingCount, onesBitmap) + } +} + +func (ra *Bitmap) fillUpBitmapContainerRange(offset uint64, minY, maxY, card int, onesBitmap bitmap) bitmap { + bitmap := bitmap(ra.getContainer(offset)) + bitmap[indexSize] = maxContainerSize + bitmap[indexType] = typeBitmap + setCardinality(bitmap, card) + + bitmap.setRange(minY, maxY, onesBitmap) + return bitmap +} + +func (ra *Bitmap) fillUpBitmapContainers(offset uint64, minIdx, maxIdx int) bitmap { + onesBitmap := bitmap(ra.getContainer(offset)[:maxContainerSize]) + onesBitmap[indexSize] = maxContainerSize + onesBitmap[indexType] = typeBitmap + setCardinality(onesBitmap, maxCardinality) + + // fill entire bitmap container with ones + onesBitmap.fillWithOnes() + + // fill remaining containers by copying first one + for i := minIdx; i < maxIdx; i++ { + key := uint64(i*maxCardinality) & mask + offset := ra.newContainerNoClr(maxContainerSize) + ra.setKey(key, offset) + copy(ra.data[offset:], onesBitmap) + } + return onesBitmap +} + +func calcFullContainersAndRemainingCounts(maxX uint64) (int, int) { + maxCard64 := uint64(maxCardinality) + + // maxX should be included, therefore +1 + containers := maxX / maxCard64 + remaining := maxX % maxCard64 + if remaining == maxCard64-1 { + containers++ + } + remaining = (remaining + 1) % maxCard64 + return int(containers), int(remaining) +} + +func (b bitmap) setRange(leftY, rightY int, onesBitmap bitmap) { + leftY16 := (leftY + 15) / 16 * 16 + rightY16 := (rightY + 1) / 16 * 16 + + // fmt.Printf(" ==> left [%d] left16 [%d] left64 [%d]\n", leftY, leftY16, (leftY+63)/64*64) + // fmt.Printf(" ==> right [%d] right16 [%d] right64 [%d]\n", rightY, rightY16, (rightY+1)/64*64) - container64 := uint16To64SliceUnsafe(container[startIdx:]) - for i := uint16(0); i < n64; i++ { + container16 := b[startIdx:] + if onesBitmap != nil { + if l, r := uint16(leftY16/16), uint16(rightY16/16); l < r { + copy(container16[l:r], onesBitmap[startIdx+l:startIdx+r]) + } + } else { + leftY64 := (leftY + 63) / 64 * 64 + rightY64 := (rightY + 1) / 64 * 64 + + if l, r := leftY64/64, rightY64/64; l < r { + container64 := uint16To64SliceUnsafe(container16) + for i := l; i < r; i++ { + // fmt.Printf(" ==> container64 i=%d\n", i) container64[i] = math.MaxUint64 } - for i := uint16(n64 * 4); i < n16; i++ { - container[startIdx+i] = math.MaxUint16 - } - for i := uint16(0); i < rem16; i++ { - container[startIdx+n16] |= bitmapMask[i] - } } + for i, r := leftY16/16, min(leftY64/16, rightY16/16); i < r; i++ { + // fmt.Printf(" ==> container16L i=%d\n", i) + container16[i] = math.MaxUint16 + } + for i, r := max(leftY16/16, rightY64/16), rightY16/16; i < r; i++ { + // fmt.Printf(" ==> container16R i=%d\n", i) + container16[i] = math.MaxUint16 + } + } + for y, r := leftY, min(leftY16, rightY+1); y < r; y++ { + // fmt.Printf(" ==> container16L i=%d bit=%d\n", y/16, y%16) + container16[y/16] |= bitmapMask[y%16] } + for y, r := max(leftY, rightY16), rightY+1; y < r; y++ { + // fmt.Printf(" ==> container16R i=%d bit=%d\n", y/16, y%16) + container16[y/16] |= bitmapMask[y%16] + } +} - return bm +func (b bitmap) fillWithOnes() { + b64 := uint16To64SliceUnsafe(b[startIdx:]) + for i := range b64 { + b64[i] = math.MaxUint64 + } } diff --git a/bitmap_opt_test.go b/bitmap_opt_test.go index d63f7e6..b8f5f3a 100644 --- a/bitmap_opt_test.go +++ b/bitmap_opt_test.go @@ -3,6 +3,7 @@ package sroar import ( "fmt" "math" + "math/bits" "math/rand" "testing" @@ -1121,6 +1122,901 @@ func TestPrefill(t *testing.T) { } } +func TestFillUp(t *testing.T) { + t.Run("nil bitmap, noop", func(t *testing.T) { + maxX := maxCardinality + 1 + var bmNil *Bitmap + bmNil.FillUp(uint64(maxX)) + + require.Nil(t, bmNil) + }) + + t.Run("empty small bitmap, resized", func(t *testing.T) { + maxX := maxCardinality + 1 + bmSmall := NewBitmap() + lenBytes := bmSmall.LenInBytes() + capBytes := bmSmall.capInBytes() + + bmSmall.FillUp(uint64(maxX)) + require.Less(t, lenBytes, bmSmall.LenInBytes()) + require.Less(t, capBytes, bmSmall.capInBytes()) + + // + 8 (key) + 2x 4100 container - 64 container + addLen := 2 * (8 + maxContainerSize*2 - minContainerSize) + require.Equal(t, lenBytes+addLen, bmSmall.LenInBytes()) + require.Equal(t, capBytes+addLen, bmSmall.capInBytes()) + + assertPrefilled(t, bmSmall, maxX) + }) + + t.Run("empty big bitmap, reused", func(t *testing.T) { + maxX := maxCardinality + 1 + bmBig := NewBitmap() + bmBig.expandNoLengthChange(3 * maxContainerSize) // big enough to fit 2x fullsize container + lenBytes := bmBig.LenInBytes() + capBytes := bmBig.capInBytes() + + bmBig.FillUp(uint64(maxX)) + require.Less(t, lenBytes, bmBig.LenInBytes()) + require.Equal(t, capBytes, bmBig.capInBytes()) + + // + 8 (key) + 2x 4100 container - 64 container + addLen := 2 * (8 + maxContainerSize*2 - minContainerSize) + require.Equal(t, lenBytes+addLen, bmBig.LenInBytes()) + + assertPrefilled(t, bmBig, maxX) + }) + + t.Run("max value already >= than given maxX, noop", func(t *testing.T) { + maxX := maxCardinality + 1 + + t.Run("prefilled", func(t *testing.T) { + bm := Prefill(uint64(maxX)) + lenBytes := bm.LenInBytes() + capBytes := bm.capInBytes() + + bm.FillUp(uint64(maxX - 10)) + require.Equal(t, lenBytes, bm.LenInBytes()) + require.Equal(t, capBytes, bm.capInBytes()) + + bm.FillUp(uint64(maxX)) + require.Equal(t, lenBytes, bm.LenInBytes()) + require.Equal(t, capBytes, bm.capInBytes()) + }) + + t.Run("single element", func(t *testing.T) { + bm := NewBitmap() + bm.Set(uint64(maxX)) + lenBytes := bm.LenInBytes() + capBytes := bm.capInBytes() + + bm.FillUp(uint64(maxX - 10)) + require.Equal(t, lenBytes, bm.LenInBytes()) + require.Equal(t, capBytes, bm.capInBytes()) + + bm.FillUp(uint64(maxX)) + require.Equal(t, lenBytes, bm.LenInBytes()) + require.Equal(t, capBytes, bm.capInBytes()) + }) + }) + + t.Run("current max value in same container as given maxX", func(t *testing.T) { + t.Run("prefilled bitmap, no resize", func(t *testing.T) { + for _, prefillX := range []int{ + 1023, 1024, 1025, 1039, 1040, 1041, + } { + for _, fillUpX := range []int{ + 4095, 4096, 4097, 4111, 4112, 4113, maxCardinality - 2, maxCardinality - 1, + } { + t.Run(fmt.Sprintf("filled up 1x %d to %d", prefillX, fillUpX), func(t *testing.T) { + prefilled := Prefill(uint64(prefillX)) + lenBytes := prefilled.LenInBytes() + capBytes := prefilled.capInBytes() + + prefilled.FillUp(uint64(fillUpX)) + require.Equal(t, lenBytes, prefilled.LenInBytes()) + require.Equal(t, capBytes, prefilled.capInBytes()) + + assertPrefilled(t, prefilled, fillUpX) + }) + + t.Run(fmt.Sprintf("filled up 3x %d to %d", prefillX, fillUpX), func(t *testing.T) { + prefilled := Prefill(uint64(prefillX)) + lenBytes := prefilled.LenInBytes() + capBytes := prefilled.capInBytes() + + prefilled.FillUp(uint64(fillUpX) - 20) + prefilled.FillUp(uint64(fillUpX) - 10) + prefilled.FillUp(uint64(fillUpX)) + require.Equal(t, lenBytes, prefilled.LenInBytes()) + require.Equal(t, capBytes, prefilled.capInBytes()) + + assertPrefilled(t, prefilled, fillUpX) + }) + } + } + }) + + t.Run("single elem array, no resize", func(t *testing.T) { + for _, currentMaxX := range []int{ + 1023, 1024, 1025, 1039, 1040, 1041, + } { + for _, fillUpX := range []int{ + 1055, 1056, 1057, 1082, + } { + t.Run(fmt.Sprintf("filled 1x %d to %d", currentMaxX, fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(currentMaxX)) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(fillUpX)) + require.Equal(t, lenBytes, singleElem.LenInBytes()) + require.Equal(t, capBytes, singleElem.capInBytes()) + + assertFilledUp(t, singleElem, currentMaxX, fillUpX) + }) + + t.Run(fmt.Sprintf("filled 3x %d to %d", currentMaxX, fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(currentMaxX)) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(fillUpX) - 10) + singleElem.FillUp(uint64(fillUpX) - 5) + singleElem.FillUp(uint64(fillUpX)) + require.Equal(t, lenBytes, singleElem.LenInBytes()) + require.Equal(t, capBytes, singleElem.capInBytes()) + + assertFilledUp(t, singleElem, currentMaxX, fillUpX) + }) + } + } + }) + + t.Run("single elem array, convert to bitmap", func(t *testing.T) { + for _, currentMaxX := range []int{ + 1023, 1024, 1025, 1039, 1040, 1041, + } { + for _, fillUpX := range []int{ + 4095, 4096, 4097, maxCardinality - 1, + } { + t.Run(fmt.Sprintf("filled 1x %d to %d", currentMaxX, fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(currentMaxX)) + singleElem.expandNoLengthChange(maxContainerSize) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(fillUpX)) + require.Less(t, lenBytes, singleElem.LenInBytes()) + require.Equal(t, capBytes, singleElem.capInBytes()) + + // + 4100 container + addLen := 2 * maxContainerSize + require.Equal(t, lenBytes+addLen, singleElem.LenInBytes()) + + assertFilledUp(t, singleElem, currentMaxX, fillUpX) + }) + + t.Run(fmt.Sprintf("filled 3x %d to %d", currentMaxX, fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(currentMaxX)) + singleElem.expandNoLengthChange(maxContainerSize) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(fillUpX) - 3040) + singleElem.FillUp(uint64(fillUpX) - 1000) + singleElem.FillUp(uint64(fillUpX)) + require.Less(t, lenBytes, singleElem.LenInBytes()) + require.Equal(t, capBytes, singleElem.capInBytes()) + + // + 4100 container + addLen := 2 * maxContainerSize + require.Equal(t, lenBytes+addLen, singleElem.LenInBytes()) + + assertFilledUp(t, singleElem, currentMaxX, fillUpX) + }) + } + } + }) + }) + + t.Run("current max value in different container than given maxX", func(t *testing.T) { + addDouble := func(prevVal int) int { return 2 * prevVal } + addContainers := func(containersCount int) func(int) int { + return func(prevVal int) int { + // Xx (8 key + 4100 container) + return prevVal + 2*containersCount*(8+maxContainerSize) + } + } + + t.Run("prefilled bitmap", func(t *testing.T) { + for _, tc := range []struct { + prefillX int + fillUpX int + fnExpAddLen func(prevLen int) (newLen int) + fnExpAddCap func(prevCap int) (newCap int) + fnExp3xAddLen func(prevLen int) (newLen int) + fnExp3xAddCap func(prevCap int) (newCap int) + }{ + { + prefillX: maxCardinality - 100, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 100, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 100, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 100, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 100, + fillUpX: 5*maxCardinality - 1, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: addContainers(4), + }, + { + prefillX: maxCardinality - 100, + fillUpX: 5 * maxCardinality, + fnExpAddLen: addContainers(5), + fnExpAddCap: addContainers(5), + fnExp3xAddLen: addContainers(5), + fnExp3xAddCap: func(prevCap int) int { + // first 4 containers were added, then cap was doubled + return addDouble(addContainers(4)(prevCap)) + }, + }, + { + prefillX: maxCardinality - 100, + fillUpX: 5*maxCardinality + 1, + fnExpAddLen: addContainers(5), + fnExpAddCap: addContainers(5), + fnExp3xAddLen: addContainers(5), + fnExp3xAddCap: func(prevCap int) int { + // first 4 containers were added, then cap was doubled + return addDouble(addContainers(4)(prevCap)) + }, + }, + + { + prefillX: maxCardinality - 50, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 50, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 50, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 50, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 50, + fillUpX: 5*maxCardinality - 1, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: addContainers(4), + }, + { + prefillX: maxCardinality - 50, + fillUpX: 5 * maxCardinality, + fnExpAddLen: addContainers(5), + fnExpAddCap: addContainers(5), + fnExp3xAddLen: addContainers(5), + fnExp3xAddCap: func(prevCap int) int { + // first 4 containers were added, then cap was doubled + return addDouble(addContainers(4)(prevCap)) + }, + }, + { + prefillX: maxCardinality - 50, + fillUpX: 5*maxCardinality + 1, + fnExpAddLen: addContainers(5), + fnExpAddCap: addContainers(5), + fnExp3xAddLen: addContainers(5), + fnExp3xAddCap: func(prevCap int) int { + // first 4 containers were added, then cap was doubled + return addDouble(addContainers(4)(prevCap)) + }, + }, + + { + prefillX: maxCardinality - 1, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 1, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 1, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 1, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(1), + fnExpAddCap: addDouble, + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addDouble, + }, + { + prefillX: maxCardinality - 1, + fillUpX: 5*maxCardinality - 1, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: addContainers(4), + }, + { + prefillX: maxCardinality - 1, + fillUpX: 5 * maxCardinality, + fnExpAddLen: addContainers(5), + fnExpAddCap: addContainers(5), + fnExp3xAddLen: addContainers(5), + fnExp3xAddCap: func(prevCap int) int { + // first 4 containers were added, then cap was doubled + return addDouble(addContainers(4)(prevCap)) + }, + }, + { + prefillX: maxCardinality - 1, + fillUpX: 5*maxCardinality + 1, + fnExpAddLen: addContainers(5), + fnExpAddCap: addContainers(5), + fnExp3xAddLen: addContainers(5), + fnExp3xAddCap: func(prevCap int) int { + // first 4 containers were added, then cap was doubled + return addDouble(addContainers(4)(prevCap)) + }, + }, + } { + t.Run(fmt.Sprintf("filled up 1x %d to %d", tc.prefillX, tc.fillUpX), func(t *testing.T) { + prefilled := Prefill(uint64(tc.prefillX)) + lenBytes := prefilled.LenInBytes() + capBytes := prefilled.capInBytes() + + prefilled.FillUp(uint64(tc.fillUpX)) + require.Equal(t, tc.fnExpAddLen(lenBytes), prefilled.LenInBytes()) + require.Equal(t, tc.fnExpAddCap(capBytes), prefilled.capInBytes()) + + assertPrefilled(t, prefilled, tc.fillUpX) + }) + + t.Run(fmt.Sprintf("filled up 3x %d to %d", tc.prefillX, tc.fillUpX), func(t *testing.T) { + prefilled := Prefill(uint64(tc.prefillX)) + lenBytes := prefilled.LenInBytes() + capBytes := prefilled.capInBytes() + + prefilled.FillUp(uint64(tc.fillUpX) - 20) + prefilled.FillUp(uint64(tc.fillUpX) - 10) + prefilled.FillUp(uint64(tc.fillUpX)) + require.Equal(t, tc.fnExp3xAddLen(lenBytes), prefilled.LenInBytes()) + require.Equal(t, tc.fnExp3xAddCap(capBytes), prefilled.capInBytes()) + + assertPrefilled(t, prefilled, tc.fillUpX) + }) + } + }) + + t.Run("single elem array, keep common array", func(t *testing.T) { + for _, tc := range []struct { + currentMaxX int + fillUpX int + fnExpAddLen func(prevLen int) (newLen int) + fnExpAddCap func(prevCap int) (newCap int) + fnExp3xAddLen func(prevLen int) (newLen int) + fnExp3xAddCap func(prevCap int) (newCap int) + }{ + { + currentMaxX: maxCardinality - 20, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 20, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 20, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 20, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 20, + fillUpX: 3*maxCardinality - 1, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 20, + fillUpX: 3 * maxCardinality, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(2)(prevCap)) + }, + }, + { + currentMaxX: maxCardinality - 20, + fillUpX: 3*maxCardinality + 1, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(2)(prevCap)) + }, + }, + + { + currentMaxX: maxCardinality - 10, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 10, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 10, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 10, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 10, + fillUpX: 3*maxCardinality - 1, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 10, + fillUpX: 3 * maxCardinality, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(2)(prevCap)) + }, + }, + { + currentMaxX: maxCardinality - 10, + fillUpX: 3*maxCardinality + 1, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(2)(prevCap)) + }, + }, + + { + currentMaxX: maxCardinality - 1, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 1, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 1, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 1, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(1), + fnExpAddCap: addContainers(1), + fnExp3xAddLen: addContainers(1), + fnExp3xAddCap: addContainers(1), + }, + { + currentMaxX: maxCardinality - 1, + fillUpX: 3*maxCardinality - 1, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 1, + fillUpX: 3 * maxCardinality, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(2)(prevCap)) + }, + }, + { + currentMaxX: maxCardinality - 1, + fillUpX: 3*maxCardinality + 1, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(2)(prevCap)) + }, + }, + } { + t.Run(fmt.Sprintf("filled up 1x %d to %d", tc.currentMaxX, tc.fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(tc.currentMaxX)) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(tc.fillUpX)) + require.Equal(t, tc.fnExpAddLen(lenBytes), singleElem.LenInBytes()) + require.Equal(t, tc.fnExpAddCap(capBytes), singleElem.capInBytes()) + + assertFilledUp(t, singleElem, tc.currentMaxX, tc.fillUpX) + }) + + t.Run(fmt.Sprintf("filled up 3x %d to %d", tc.currentMaxX, tc.fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(tc.currentMaxX)) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(tc.fillUpX) - 20) + singleElem.FillUp(uint64(tc.fillUpX) - 10) + singleElem.FillUp(uint64(tc.fillUpX)) + require.Equal(t, tc.fnExp3xAddLen(lenBytes), singleElem.LenInBytes()) + require.Equal(t, tc.fnExp3xAddCap(capBytes), singleElem.capInBytes()) + + assertFilledUp(t, singleElem, tc.currentMaxX, tc.fillUpX) + }) + } + }) + + t.Run("single elem array, convert common to bitmap", func(t *testing.T) { + for _, tc := range []struct { + currentMaxX int + fillUpX int + fnExpAddLen func(prevLen int) (newLen int) + fnExpAddCap func(prevCap int) (newCap int) + fnExp3xAddLen func(prevLen int) (newLen int) + fnExp3xAddCap func(prevCap int) (newCap int) + }{ + { + currentMaxX: maxCardinality - 200, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: func(prevLen int) int { + return addContainers(2)(prevLen) - 2*8 + }, + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(1)(prevCap) - 2*8) + }, + }, + { + currentMaxX: maxCardinality - 200, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 200, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 200, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 200, + fillUpX: 3*maxCardinality - 1, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: addContainers(3), + }, + { + currentMaxX: maxCardinality - 200, + fillUpX: 3 * maxCardinality, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(3)(prevCap)) + }, + }, + { + currentMaxX: maxCardinality - 200, + fillUpX: 3*maxCardinality + 1, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(3)(prevCap)) + }, + }, + + { + currentMaxX: maxCardinality - 150, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: func(prevLen int) int { + return addContainers(2)(prevLen) - 2*8 + }, + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(1)(prevCap) - 2*8) + }, + }, + { + currentMaxX: maxCardinality - 150, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 150, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 150, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 150, + fillUpX: 3*maxCardinality - 1, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: addContainers(3), + }, + { + currentMaxX: maxCardinality - 150, + fillUpX: 3 * maxCardinality, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(3)(prevCap)) + }, + }, + { + currentMaxX: maxCardinality - 150, + fillUpX: 3*maxCardinality + 1, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(3)(prevCap)) + }, + }, + + { + currentMaxX: maxCardinality - 100, + fillUpX: maxCardinality, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: func(prevLen int) int { + return addContainers(2)(prevLen) - 2*8 + }, + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(1)(prevCap) - 2*8) + }, + }, + { + currentMaxX: maxCardinality - 100, + fillUpX: maxCardinality + 1022, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 100, + fillUpX: maxCardinality + 1023, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 100, + fillUpX: maxCardinality + 1024, + fnExpAddLen: addContainers(2), + fnExpAddCap: addContainers(2), + fnExp3xAddLen: addContainers(2), + fnExp3xAddCap: addContainers(2), + }, + { + currentMaxX: maxCardinality - 100, + fillUpX: 3*maxCardinality - 1, + fnExpAddLen: addContainers(3), + fnExpAddCap: addContainers(3), + fnExp3xAddLen: addContainers(3), + fnExp3xAddCap: addContainers(3), + }, + { + currentMaxX: maxCardinality - 100, + fillUpX: 3 * maxCardinality, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(3)(prevCap)) + }, + }, + { + currentMaxX: maxCardinality - 100, + fillUpX: 3*maxCardinality + 1, + fnExpAddLen: addContainers(4), + fnExpAddCap: addContainers(4), + fnExp3xAddLen: addContainers(4), + fnExp3xAddCap: func(prevCap int) int { + return addDouble(addContainers(3)(prevCap)) + }, + }, + } { + t.Run(fmt.Sprintf("filled up 1x %d to %d", tc.currentMaxX, tc.fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(tc.currentMaxX)) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(tc.fillUpX)) + require.Equal(t, tc.fnExpAddLen(lenBytes), singleElem.LenInBytes()) + require.Equal(t, tc.fnExpAddCap(capBytes), singleElem.capInBytes()) + + assertFilledUp(t, singleElem, tc.currentMaxX, tc.fillUpX) + }) + + t.Run(fmt.Sprintf("filled up 3x %d to %d", tc.currentMaxX, tc.fillUpX), func(t *testing.T) { + singleElem := NewBitmap() + singleElem.Set(uint64(tc.currentMaxX)) + lenBytes := singleElem.LenInBytes() + capBytes := singleElem.capInBytes() + + singleElem.FillUp(uint64(tc.fillUpX) - 20) + singleElem.FillUp(uint64(tc.fillUpX) - 10) + singleElem.FillUp(uint64(tc.fillUpX)) + require.Equal(t, tc.fnExp3xAddLen(lenBytes), singleElem.LenInBytes()) + require.Equal(t, tc.fnExp3xAddCap(capBytes), singleElem.capInBytes()) + + assertFilledUp(t, singleElem, tc.currentMaxX, tc.fillUpX) + }) + } + }) + }) +} + func assertPrefilled(t *testing.T, bm *Bitmap, maxX int) { require.Equal(t, maxX+1, bm.GetCardinality()) @@ -1132,6 +2028,151 @@ func assertPrefilled(t *testing.T, bm *Bitmap, maxX int) { } } +func assertFilledUp(t *testing.T, bm *Bitmap, minX, maxX int) { + require.Equal(t, maxX-minX+1, bm.GetCardinality()) + + arr := bm.ToArray() + require.Equal(t, maxX-minX+1, len(arr)) + + for i, x := range arr { + require.Equal(t, uint64(i+minX), x) + } +} + +func TestPrefillUtils(t *testing.T) { + t.Run("calcNoFullContainerAndRemainingXs", func(t *testing.T) { + maxCard64 := uint64(maxCardinality) + + for _, tc := range []struct { + maxX uint64 + expNoContainers int + expNoRemaining int + }{ + { + maxX: 1, + expNoContainers: 0, + expNoRemaining: 2, + }, + { + maxX: maxCard64 - 2, + expNoContainers: 0, + expNoRemaining: maxCardinality - 1, + }, + { + maxX: maxCard64 - 1, + expNoContainers: 1, + expNoRemaining: 0, + }, + { + maxX: maxCard64, + expNoContainers: 1, + expNoRemaining: 1, + }, + { + maxX: maxCard64 + 1, + expNoContainers: 1, + expNoRemaining: 2, + }, + { + maxX: 4*maxCard64 - 2, + expNoContainers: 3, + expNoRemaining: maxCardinality - 1, + }, + { + maxX: 4*maxCard64 - 1, + expNoContainers: 4, + expNoRemaining: 0, + }, + { + maxX: 4 * maxCard64, + expNoContainers: 4, + expNoRemaining: 1, + }, + { + maxX: 4*maxCard64 + 1, + expNoContainers: 4, + expNoRemaining: 2, + }, + } { + t.Run(fmt.Sprintf("maxX %d", tc.maxX), func(t *testing.T) { + containers, remaining := calcFullContainersAndRemainingCounts(tc.maxX) + require.Equal(t, tc.expNoContainers, containers) + require.Equal(t, tc.expNoRemaining, remaining) + }) + } + }) + + t.Run("setRange", func(t *testing.T) { + newContainerBitmap := func() bitmap { + return bitmap(make([]uint16, maxContainerSize)) + } + + onesBitmap := newContainerBitmap() + onesBitmap.fillWithOnes() + + assertOnes := func(t *testing.T, b bitmap, minY, maxY int) { + count := 0 + for _, v := range uint16To64SliceUnsafe(b[startIdx:]) { + count += bits.OnesCount64(v) + } + require.Equal(t, maxY-minY+1, count) + + for i := uint16(minY); i <= uint16(maxY); i++ { + require.True(t, b.has(i)) + } + } + + type testCase struct { + minY, maxY int + } + testCases := []testCase{ + {minY: 0, maxY: 0}, + {minY: 1, maxY: 11}, + {minY: 2345, maxY: 4567}, + {minY: 4086, maxY: 4096}, + } + for _, pair := range [][2]int{ + {16, 48}, + {128, 320}, + {112, 384}, + {192, 336}, + } { + for i := -2; i <= 2; i++ { + for j := -2; j <= 2; j++ { + testCases = append(testCases, testCase{ + minY: pair[0] + i, + maxY: pair[1] + j, + }) + } + } + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("minY %d - maxY %d, without ones bitmap", tc.minY, tc.maxY), func(t *testing.T) { + b := newContainerBitmap() + b.setRange(tc.minY, tc.maxY, nil) + + assertOnes(t, b, tc.minY, tc.maxY) + }) + t.Run(fmt.Sprintf("minY %d - maxY %d, with ones bitmap", tc.minY, tc.maxY), func(t *testing.T) { + b := newContainerBitmap() + b.setRange(tc.minY, tc.maxY, onesBitmap) + + assertOnes(t, b, tc.minY, tc.maxY) + }) + } + }) + + t.Run("fillWithOnes", func(t *testing.T) { + b := bitmap(make([]uint16, maxContainerSize)) + b.fillWithOnes() + + for _, v := range uint16To64SliceUnsafe(b[startIdx:]) { + require.Equal(t, 64, bits.OnesCount64(v)) + } + }) +} + func TestMergeToSuperset(t *testing.T) { run := func(t *testing.T, bufs [][]uint16) { containerThreshold := uint64(math.MaxUint16 + 1) diff --git a/keys.go b/keys.go index 7fe1ca6..647786e 100644 --- a/keys.go +++ b/keys.go @@ -89,6 +89,21 @@ func (n node) search(k uint64) int { // return int(simd.Search(n[keyOffset(0):keyOffset(N)], k)) } +// Search returns the index of a smallest key >= k in a node. +// Runs from highest to smallest key. +func (n node) searchReverse(k uint64) int { + N := n.numKeys() + idx := N + + for i := N - 1; i >= 0; i-- { + if n.key(i) < k { + break + } + idx = i + } + return idx +} + func zeroOut(data []uint64) { for i := 0; i < len(data); i++ { data[i] = 0