chunks: don't mutate in bstream iterator

This replaces mutation of underlying bytes in the iterated slice
with a shift counter, which is used when reading the head byte.
This is avoids having to copy the entire slice for every new iterator.
This commit is contained in:
Fabian Reinartz 2016-11-30 19:08:28 +01:00
parent f392c01c12
commit c9ee572709
2 changed files with 55 additions and 64 deletions

View File

@ -1,18 +1,14 @@
package chunks package chunks
import ( import "io"
"bytes"
"encoding/binary"
"io"
)
// bstream is a stream of bits // bstream is a stream of bits
type bstream struct { type bstream struct {
// the data stream // the data stream
stream []byte stream []byte
// how many bits are valid in current byte count uint8 // how many bits are valid in current byte
count uint8 shift uint8 // pos of next bit in current byte
} }
func newBReader(b []byte) *bstream { func newBReader(b []byte) *bstream {
@ -41,7 +37,6 @@ const (
) )
func (b *bstream) writeBit(bit bit) { func (b *bstream) writeBit(bit bit) {
if b.count == 0 { if b.count == 0 {
b.stream = append(b.stream, 0) b.stream = append(b.stream, 0)
b.count = 8 b.count = 8
@ -57,7 +52,6 @@ func (b *bstream) writeBit(bit bit) {
} }
func (b *bstream) writeByte(byt byte) { func (b *bstream) writeByte(byt byte) {
if b.count == 0 { if b.count == 0 {
b.stream = append(b.stream, 0) b.stream = append(b.stream, 0)
b.count = 8 b.count = 8
@ -89,14 +83,22 @@ func (b *bstream) writeBits(u uint64, nbits int) {
} }
} }
func (b *bstream) readBit() (bit, error) { func (b *bstream) headByte() byte {
return b.stream[0] << b.shift
}
func (b *bstream) advance() {
b.stream = b.stream[1:]
b.shift = 0
}
func (b *bstream) readBit() (bit, error) {
if len(b.stream) == 0 { if len(b.stream) == 0 {
return false, io.EOF return false, io.EOF
} }
if b.count == 0 { if b.count == 0 {
b.stream = b.stream[1:] b.advance()
// did we just run out of stuff to read? // did we just run out of stuff to read?
if len(b.stream) == 0 { if len(b.stream) == 0 {
return false, io.EOF return false, io.EOF
@ -104,20 +106,19 @@ func (b *bstream) readBit() (bit, error) {
b.count = 8 b.count = 8
} }
d := b.headByte() & 0x80
b.count-- b.count--
d := b.stream[0] & 0x80 b.shift++
b.stream[0] <<= 1
return d != 0, nil return d != 0, nil
} }
func (b *bstream) readByte() (byte, error) { func (b *bstream) readByte() (byte, error) {
if len(b.stream) == 0 { if len(b.stream) == 0 {
return 0, io.EOF return 0, io.EOF
} }
if b.count == 0 { if b.count == 0 {
b.stream = b.stream[1:] b.advance()
if len(b.stream) == 0 { if len(b.stream) == 0 {
return 0, io.EOF return 0, io.EOF
@ -128,18 +129,19 @@ func (b *bstream) readByte() (byte, error) {
if b.count == 8 { if b.count == 8 {
b.count = 0 b.count = 0
return b.stream[0], nil return b.headByte(), nil
} }
byt := b.stream[0] byt := b.headByte()
b.stream = b.stream[1:] b.advance()
if len(b.stream) == 0 { if len(b.stream) == 0 {
return 0, io.EOF return 0, io.EOF
} }
// We just advanced the stream and can assume the shift to be 0.
byt |= b.stream[0] >> b.count byt |= b.stream[0] >> b.count
b.stream[0] <<= (8 - b.count) b.shift = 8 - b.count
return byt, nil return byt, nil
} }
@ -162,9 +164,9 @@ func (b *bstream) readBits(nbits int) (uint64, error) {
} }
if nbits > int(b.count) { if nbits > int(b.count) {
u = (u << uint(b.count)) | uint64(b.stream[0]>>(8-b.count)) u = (u << uint(b.count)) | uint64(b.headByte()>>(8-b.count))
nbits -= int(b.count) nbits -= int(b.count)
b.stream = b.stream[1:] b.advance()
if len(b.stream) == 0 { if len(b.stream) == 0 {
return 0, io.EOF return 0, io.EOF
@ -172,37 +174,8 @@ func (b *bstream) readBits(nbits int) (uint64, error) {
b.count = 8 b.count = 8
} }
u = (u << uint(nbits)) | uint64(b.stream[0]>>(8-uint(nbits))) u = (u << uint(nbits)) | uint64(b.headByte()>>(8-uint(nbits)))
b.stream[0] <<= uint(nbits) b.shift = b.shift + uint8(nbits)
b.count -= uint8(nbits) b.count -= uint8(nbits)
return u, nil return u, nil
} }
// MarshalBinary implements the encoding.BinaryMarshaler interface
func (b *bstream) MarshalBinary() ([]byte, error) {
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.BigEndian, b.count)
if err != nil {
return nil, err
}
err = binary.Write(buf, binary.BigEndian, b.stream)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
func (b *bstream) UnmarshalBinary(bIn []byte) error {
buf := bytes.NewReader(bIn)
err := binary.Read(buf, binary.BigEndian, &b.count)
if err != nil {
return err
}
b.stream = make([]byte, buf.Len())
err = binary.Read(buf, binary.BigEndian, &b.stream)
if err != nil {
return err
}
return nil
}

View File

@ -20,18 +20,20 @@ func TestChunk(t *testing.T) {
EncXOR: func(sz int) Chunk { return NewXORChunk(sz) }, EncXOR: func(sz int) Chunk { return NewXORChunk(sz) },
} { } {
t.Run(fmt.Sprintf("%s", enc), func(t *testing.T) { t.Run(fmt.Sprintf("%s", enc), func(t *testing.T) {
for range make([]struct{}, 10000) { for range make([]struct{}, 3000) {
c := nc(rand.Intn(512)) c := nc(rand.Intn(1024))
testChunk(t, c) if err := testChunk(c); err != nil {
t.Fatal(err)
}
} }
}) })
} }
} }
func testChunk(t *testing.T, c Chunk) { func testChunk(c Chunk) error {
app, err := c.Appender() app, err := c.Appender()
if err != nil { if err != nil {
t.Fatal(err) return err
} }
var exp []pair var exp []pair
@ -39,18 +41,31 @@ func testChunk(t *testing.T, c Chunk) {
ts = int64(1234123324) ts = int64(1234123324)
v = 1243535.123 v = 1243535.123
) )
for { i := 0
for i < 3 {
ts += int64(rand.Intn(10000) + 1) ts += int64(rand.Intn(10000) + 1)
v = rand.Float64() v = rand.Float64()
// v += float64(100)
err := app.Append(ts, v) // Start with a new appender every 10th sample. This emulates starting
// appending to a partially filled chunk.
if i%10 == 0 {
app, err = c.Appender()
if err != nil {
return err
}
}
err = app.Append(ts, v)
if err != nil { if err != nil {
if err == ErrChunkFull { if err == ErrChunkFull {
break break
} }
t.Fatal(err) return err
} }
exp = append(exp, pair{t: ts, v: v}) exp = append(exp, pair{t: ts, v: v})
i++
// fmt.Println("appended", len(c.Bytes()), c.Bytes())
} }
it := c.Iterator() it := c.Iterator()
@ -60,11 +75,12 @@ func testChunk(t *testing.T, c Chunk) {
res = append(res, pair{t: ts, v: v}) res = append(res, pair{t: ts, v: v})
} }
if it.Err() != nil { if it.Err() != nil {
t.Fatal(it.Err()) return it.Err()
} }
if !reflect.DeepEqual(exp, res) { if !reflect.DeepEqual(exp, res) {
t.Fatalf("unexpected result\n\ngot: %v\n\nexp: %v", res, exp) return fmt.Errorf("unexpected result\n\ngot: %v\n\nexp: %v", res, exp)
} }
return nil
} }
func benchmarkIterator(b *testing.B, newChunk func(int) Chunk) { func benchmarkIterator(b *testing.B, newChunk func(int) Chunk) {
@ -75,7 +91,8 @@ func benchmarkIterator(b *testing.B, newChunk func(int) Chunk) {
var exp []pair var exp []pair
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
t += int64(rand.Intn(10000) + 1) t += int64(rand.Intn(10000) + 1)
v = rand.Float64() // v = rand.Float64()
v += float64(100)
exp = append(exp, pair{t: t, v: v}) exp = append(exp, pair{t: t, v: v})
} }
@ -140,7 +157,8 @@ func benchmarkAppender(b *testing.B, newChunk func(int) Chunk) {
var exp []pair var exp []pair
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
t += int64(rand.Intn(10000) + 1) t += int64(rand.Intn(10000) + 1)
v = rand.Float64() // v = rand.Float64()
v += float64(100)
exp = append(exp, pair{t: t, v: v}) exp = append(exp, pair{t: t, v: v})
} }