From 7e9fc3953d95e45cb063a1c222a6d04d72c32b51 Mon Sep 17 00:00:00 2001 From: Alan Bernstein Date: Tue, 6 Jun 2017 15:13:48 -0500 Subject: [PATCH] Support RLE in writing and reading snapshots --- roaring/roaring.go | 100 +++++++++++++++++++++++++------ roaring/roaring_internal_test.go | 41 +++++++++++++ 2 files changed, 124 insertions(+), 17 deletions(-) diff --git a/roaring/roaring.go b/roaring/roaring.go index 702f4460d..6ef1e17d3 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -27,10 +27,12 @@ import ( const ( // cookie is the first four bytes in a roaring bitmap file. - cookie = uint32(12346) + cookieNoRuns = uint32(12346) + cookie = uint32(12347) - // headerSize is the size of the cookie and key count at the beginning of a file. - headerSize = 4 + 4 + // headerBaseSize is the size of the cookie and key count at the beginning of a file. + // Headers in files with runs also include runFlagBitset, of length (numContainers+7)/8. + headerBaseSize = 4 + 4 // bitmapN is the number of values in a container.bitmap. bitmapN = (1 << 16) / 64 @@ -517,10 +519,41 @@ func (b *Bitmap) WriteTo(w io.Writer) (n int64, err error) { //b.removeEmptyContainers() containerCount := len(b.keys) - b.countEmptyContainers() + // Create bitset indicating runs, record whether any runs present. + containsRuns := false + runFlagBitset := make([]uint8, (containerCount+7)/8) // TODO verify size + var k uint8 = 0 + for _, c := range b.containers { + if c.n == 0 { + continue + } + if c.isRun() { + containsRuns = true + runFlagBitset[k/8] |= (1 << (k%8)) // TODO verify + } + k++ + } + + thisCookie := cookieNoRuns + headerSize := headerBaseSize + if containsRuns { + thisCookie = cookie + headerSize += len(runFlagBitset) + } + // Build header before writing individual container blocks. buf := make([]byte, headerSize+(containerCount*(4+8+4))) - binary.LittleEndian.PutUint32(buf[0:], cookie) + binary.LittleEndian.PutUint32(buf[0:], thisCookie) + // TODO supposed to be 2 bytes, but doesnt make sense for >65535 containers binary.LittleEndian.PutUint32(buf[4:], uint32(containerCount)) + + if containsRuns { + // Write runFlag bitset. + for i, b := range runFlagBitset { + buf[8+i] = b + } + } + empty := 0 // Encode keys and cardinality. for i, key := range b.keys { @@ -538,6 +571,7 @@ func (b *Bitmap) WriteTo(w io.Writer) (n int64, err error) { } } + // TODO this should now be done conditionally // Write the offset for each container block. offset := uint32(len(buf)) empty = 0 @@ -574,12 +608,18 @@ func (b *Bitmap) WriteTo(w io.Writer) (n int64, err error) { // UnmarshalBinary decodes b from a binary-encoded byte slice. func (b *Bitmap) UnmarshalBinary(data []byte) error { - if len(data) < headerSize { + if len(data) < headerBaseSize { return errors.New("data too small") } - // Verify the first 4 bytes are the correct cookie. - if v := binary.LittleEndian.Uint32(data[0:4]); v != cookie { + // Verify the first 4 bytes are a valid cookie. + v := binary.LittleEndian.Uint32(data[0:4]) + containsRuns := false + if v == cookieNoRuns { + // noop + } else if v == cookie { + containsRuns = true + } else { return errors.New("invalid roaring file") } @@ -588,17 +628,28 @@ func (b *Bitmap) UnmarshalBinary(data []byte) error { b.keys = make([]uint64, keyN) b.containers = make([]*container, keyN) + headerSize := headerBaseSize + + runFlagBitset := make([]uint8, (keyN+7)/8) // TODO verify size + if containsRuns { + // Read runFlag bitset. + for i := 0; i 4096 bits + cb := &container{bitmap: make([]uint64, bitmapN), n: 129*32} + for i := 0; i < 129 ; i++ { + cb.bitmap[i] = 0x5555555555555555 + } + bb := &Bitmap{keys: []uint64{0}, containers: []*container{cb}} + bb2 := &Bitmap{} + var buf bytes.Buffer + bb.WriteTo(&buf) + bb2.UnmarshalBinary(buf.Bytes()) + if !reflect.DeepEqual(bb2.containers[0].bitmap, cb.bitmap) { + t.Fatalf("bitmap test expected %x, but got %x", cb.bitmap, bb2.containers[0].bitmap) + } +} + +func TestWriteReadRun(t *testing.T) { + cr := &container{runs: []interval32{{start: 3, last: 13}, {start: 100, last:109}}, n: 20} + br := &Bitmap{keys: []uint64{0}, containers: []*container{cr}} + br2 := &Bitmap{} + var buf bytes.Buffer + br.WriteTo(&buf) + br2.UnmarshalBinary(buf.Bytes()) + if !reflect.DeepEqual(br2.containers[0].runs, cr.runs) { + t.Fatalf("run test expected %x, but got %x", cr.runs, br2.containers[0].runs) + } +}