Skip to content

Commit 76dc3f5

Browse files
committed
Document that RunOptimize recovers space after modifying run containers
1 parent 3cf1fa5 commit 76dc3f5

7 files changed

Lines changed: 64 additions & 4 deletions

File tree

‎BitSliceIndexing/bsi.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ func NewDefaultBSI() *BSI {
5454
return NewBSI(int64(0), int64(0))
5555
}
5656

57-
// RunOptimize attempts to further compress the runs of consecutive values found in the bitmap
57+
// RunOptimize converts the containers of the underlying bitmaps to their most compact
58+
// encoding. Calling it again after modifying the BSI can recover space: see
59+
// Bitmap.RunOptimize.
5860
func (b *BSI) RunOptimize() {
5961
b.eBM.RunOptimize()
6062
for i := 0; i < len(b.bA); i++ {

‎README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,15 @@ That is, given a fixed overhead for the universe size (x), Roaring
280280
bitmaps never use more than 2 bytes per integer. You can call
281281
``BoundSerializedSizeInBytes`` for a more precise estimate.
282282

283+
Call ``RunOptimize`` to convert containers to their most compact encoding,
284+
including run-length encoding where it helps. Some operations that modify a
285+
bitmap, such as ``AddRange`` and ``RemoveRange``, do not always convert a run
286+
container back to an array or bitmap container when the runs stop being the
287+
most compact encoding. In the worst case, the runs of one 65536-value chunk can
288+
take about 128 KiB where a bitmap container takes 8 KiB. Calling ``RunOptimize``
289+
again after such modifications recovers that space, in memory and once
290+
serialized. This applies to 64-bit bitmaps (``roaring64``) as well.
291+
283292
### 64-bit Roaring
284293

285294
By default, roaring is used to stored unsigned 32-bit integers. However, we also offer

‎roaring.go‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,16 @@ func (rb *Bitmap) FromBuffer(buf []byte) (p int64, err error) {
394394
return
395395
}
396396

397-
// RunOptimize attempts to further compress the runs of consecutive values found in the bitmap
397+
// RunOptimize converts each container to its most compact encoding: run-length encoding
398+
// where it is smaller, and an array or bitmap container where it is not.
399+
//
400+
// Some operations that modify a bitmap, such as AddRange and RemoveRange, do not always
401+
// convert a run container back to an array or bitmap container when the runs stop being
402+
// the most compact encoding. Such a container can then use more memory and serialized
403+
// space than needed: in the worst case, the runs of one 65536-value chunk can take about
404+
// 128 KiB where a bitmap container takes 8 KiB. Calling RunOptimize again after such
405+
// modifications recovers that space. Consider calling it before serializing a bitmap that
406+
// was modified since its last call.
398407
func (rb *Bitmap) RunOptimize() {
399408
rb.highlowcontainer.runOptimize()
400409
}

‎roaring64/bsi64.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ func NewDefaultBSI() *BSI {
5151
return NewBSI(int64(0), int64(0))
5252
}
5353

54-
// RunOptimize attempts to further compress the runs of consecutive values found in the bitmap
54+
// RunOptimize converts the containers of the underlying bitmaps to their most compact
55+
// encoding. Calling it again after modifying the BSI can recover space: see
56+
// Bitmap.RunOptimize.
5557
func (b *BSI) RunOptimize() {
5658
b.eBM.RunOptimize()
5759
for i := 0; i < len(b.bA); i++ {

‎roaring64/roaring64.go‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,16 @@ func (rb *Bitmap) UnmarshalBinary(data []byte) error {
187187
return err
188188
}
189189

190-
// RunOptimize attempts to further compress the runs of consecutive values found in the bitmap
190+
// RunOptimize converts each container to its most compact encoding: run-length encoding
191+
// where it is smaller, and an array or bitmap container where it is not.
192+
//
193+
// Some operations that modify a bitmap, such as AddRange and RemoveRange, do not always
194+
// convert a run container back to an array or bitmap container when the runs stop being
195+
// the most compact encoding. Such a container can then use more memory and serialized
196+
// space than needed: in the worst case, the runs of one 65536-value chunk can take about
197+
// 128 KiB where a bitmap container takes 8 KiB. Calling RunOptimize again after such
198+
// modifications recovers that space. Consider calling it before serializing a bitmap that
199+
// was modified since its last call.
191200
func (rb *Bitmap) RunOptimize() {
192201
rb.highlowcontainer.runOptimize()
193202
}

‎roaring64/roaring64_test.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,3 +2141,17 @@ func TestRoaringArray64SortOrder(t *testing.T) {
21412141
assert.True(t, bm.highlowcontainer.checkKeysSorted())
21422142
})
21432143
}
2144+
2145+
func TestRunOptimizeRecoversSpaceAfterRemoveRange(t *testing.T) {
2146+
bm := New()
2147+
bm.AddRange(0, 65536)
2148+
for i := uint64(0); i < 65536; i += 2 {
2149+
bm.RemoveRange(i, i+1) // each remaining value becomes a run of length one
2150+
}
2151+
expected := bm.Clone()
2152+
before := bm.GetSerializedSizeInBytes()
2153+
bm.RunOptimize()
2154+
assert.Less(t, bm.GetSerializedSizeInBytes(), before/10)
2155+
assert.False(t, bm.HasRunCompression())
2156+
assert.True(t, expected.Equals(bm))
2157+
}

‎roaring_test.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4211,3 +4211,18 @@ func TestValidateAcceptsRunContainersThatAreNotTheSmallestEncoding(t *testing.T)
42114211
assert.NoError(t, deserialized.Validate())
42124212
assert.True(t, expected.Equals(deserialized))
42134213
}
4214+
4215+
func TestRunOptimizeRecoversSpaceAfterRemoveRange(t *testing.T) {
4216+
bm := New()
4217+
bm.AddRange(0, 65536)
4218+
for i := uint64(0); i < 65536; i += 2 {
4219+
bm.RemoveRange(i, i+1) // each remaining value becomes a run of length one
4220+
}
4221+
expected := bm.Clone()
4222+
before := bm.GetSerializedSizeInBytes()
4223+
bm.RunOptimize()
4224+
assert.Less(t, bm.GetSerializedSizeInBytes(), before/10)
4225+
assert.False(t, bm.HasRunCompression())
4226+
assert.True(t, expected.Equals(bm))
4227+
require.NoError(t, bm.Validate())
4228+
}

0 commit comments

Comments
 (0)