From 4a1b84f8b2df50a896f60710200c0a8f06bb9990 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Mon, 18 Oct 2021 18:10:12 +0200 Subject: [PATCH 1/3] chunkenc: make xor writing more DRY Signed-off-by: beorn7 --- tsdb/chunkenc/histogram.go | 37 +--------------------------- tsdb/chunkenc/xor.go | 50 ++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 57 deletions(-) diff --git a/tsdb/chunkenc/histogram.go b/tsdb/chunkenc/histogram.go index e686771292..cf0afc3d4c 100644 --- a/tsdb/chunkenc/histogram.go +++ b/tsdb/chunkenc/histogram.go @@ -16,7 +16,6 @@ package chunkenc import ( "encoding/binary" "math" - "math/bits" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/pkg/value" @@ -534,41 +533,7 @@ func (a *HistogramAppender) Recode( } func (a *HistogramAppender) writeSumDelta(v float64) { - vDelta := math.Float64bits(v) ^ math.Float64bits(a.sum) - - if vDelta == 0 { - a.b.writeBit(zero) - return - } - a.b.writeBit(one) - - leading := uint8(bits.LeadingZeros64(vDelta)) - trailing := uint8(bits.TrailingZeros64(vDelta)) - - // Clamp number of leading zeros to avoid overflow when encoding. - if leading >= 32 { - leading = 31 - } - - if a.leading != 0xff && leading >= a.leading && trailing >= a.trailing { - a.b.writeBit(zero) - a.b.writeBits(vDelta>>a.trailing, 64-int(a.leading)-int(a.trailing)) - } else { - a.leading, a.trailing = leading, trailing - - a.b.writeBit(one) - a.b.writeBits(uint64(leading), 5) - - // Note that if leading == trailing == 0, then sigbits == 64. - // But that value doesn't actually fit into the 6 bits we have. - // Luckily, we never need to encode 0 significant bits, since - // that would put us in the other case (vdelta == 0). So - // instead we write out a 0 and adjust it back to 64 on - // unpacking. - sigbits := 64 - leading - trailing - a.b.writeBits(uint64(sigbits), 6) - a.b.writeBits(vDelta>>trailing, int(sigbits)) - } + a.leading, a.trailing = xorWrite(a.b, v, a.sum, a.leading, a.trailing) } type histogramIterator struct { diff --git a/tsdb/chunkenc/xor.go b/tsdb/chunkenc/xor.go index e3d2b89764..4712b589f5 100644 --- a/tsdb/chunkenc/xor.go +++ b/tsdb/chunkenc/xor.go @@ -218,38 +218,46 @@ func bitRange(x int64, nbits uint8) bool { } func (a *xorAppender) writeVDelta(v float64) { - vDelta := math.Float64bits(v) ^ math.Float64bits(a.v) + a.leading, a.trailing = xorWrite(a.b, v, a.v, a.leading, a.trailing) +} - if vDelta == 0 { - a.b.writeBit(zero) +func xorWrite( + b *bstream, + current, previous float64, + currentLeading, currentTrailing uint8, +) (newLeading, newTrailing uint8) { + delta := math.Float64bits(current) ^ math.Float64bits(previous) + + if delta == 0 { + b.writeBit(zero) return } - a.b.writeBit(one) + b.writeBit(one) - leading := uint8(bits.LeadingZeros64(vDelta)) - trailing := uint8(bits.TrailingZeros64(vDelta)) + leading := uint8(bits.LeadingZeros64(delta)) + trailing := uint8(bits.TrailingZeros64(delta)) // Clamp number of leading zeros to avoid overflow when encoding. if leading >= 32 { leading = 31 } - if a.leading != 0xff && leading >= a.leading && trailing >= a.trailing { - a.b.writeBit(zero) - a.b.writeBits(vDelta>>a.trailing, 64-int(a.leading)-int(a.trailing)) - } else { - a.leading, a.trailing = leading, trailing - - a.b.writeBit(one) - a.b.writeBits(uint64(leading), 5) - - // Note that if leading == trailing == 0, then sigbits == 64. But that value doesn't actually fit into the 6 bits we have. - // Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0). - // So instead we write out a 0 and adjust it back to 64 on unpacking. - sigbits := 64 - leading - trailing - a.b.writeBits(uint64(sigbits), 6) - a.b.writeBits(vDelta>>trailing, int(sigbits)) + if currentLeading != 0xff && leading >= currentLeading && trailing >= currentTrailing { + b.writeBit(zero) + b.writeBits(delta>>currentTrailing, 64-int(currentLeading)-int(currentTrailing)) + return currentLeading, currentTrailing } + + b.writeBit(one) + b.writeBits(uint64(leading), 5) + + // Note that if leading == trailing == 0, then sigbits == 64. But that value doesn't actually fit into the 6 bits we have. + // Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0). + // So instead we write out a 0 and adjust it back to 64 on unpacking. + sigbits := 64 - leading - trailing + b.writeBits(uint64(sigbits), 6) + b.writeBits(delta>>trailing, int(sigbits)) + return leading, trailing } type xorIterator struct { From 78ef9c63597a6620e2bb475845bc38417d523e79 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Mon, 18 Oct 2021 18:46:08 +0200 Subject: [PATCH 2/3] chunkenc: make xor reading more DRY Signed-off-by: beorn7 --- tsdb/chunkenc/histogram.go | 62 +---------- tsdb/chunkenc/xor.go | 206 +++++++++++++++++++------------------ 2 files changed, 109 insertions(+), 159 deletions(-) diff --git a/tsdb/chunkenc/histogram.go b/tsdb/chunkenc/histogram.go index cf0afc3d4c..cff7d5cc67 100644 --- a/tsdb/chunkenc/histogram.go +++ b/tsdb/chunkenc/histogram.go @@ -838,69 +838,11 @@ func (it *histogramIterator) Next() bool { } func (it *histogramIterator) readSum() bool { - bit, err := it.br.readBitFast() - if err != nil { - bit, err = it.br.readBit() - } + sum, leading, trailing, err := xorRead(&it.br, it.sum, it.leading, it.trailing) if err != nil { it.err = err return false } - - if bit == zero { - return true // it.sum = it.sum - } - - bit, err = it.br.readBitFast() - if err != nil { - bit, err = it.br.readBit() - } - if err != nil { - it.err = err - return false - } - if bit == zero { - // Reuse leading/trailing zero bits. - // it.leading, it.trailing = it.leading, it.trailing - } else { - bits, err := it.br.readBitsFast(5) - if err != nil { - bits, err = it.br.readBits(5) - } - if err != nil { - it.err = err - return false - } - it.leading = uint8(bits) - - bits, err = it.br.readBitsFast(6) - if err != nil { - bits, err = it.br.readBits(6) - } - if err != nil { - it.err = err - return false - } - mbits := uint8(bits) - // 0 significant bits here means we overflowed and we actually - // need 64; see comment in encoder. - if mbits == 0 { - mbits = 64 - } - it.trailing = 64 - it.leading - mbits - } - - mbits := 64 - it.leading - it.trailing - bits, err := it.br.readBitsFast(mbits) - if err != nil { - bits, err = it.br.readBits(mbits) - } - if err != nil { - it.err = err - return false - } - vbits := math.Float64bits(it.sum) - vbits ^= bits << it.trailing - it.sum = math.Float64frombits(vbits) + it.sum, it.leading, it.trailing = sum, leading, trailing return true } diff --git a/tsdb/chunkenc/xor.go b/tsdb/chunkenc/xor.go index 4712b589f5..d390806b31 100644 --- a/tsdb/chunkenc/xor.go +++ b/tsdb/chunkenc/xor.go @@ -221,45 +221,6 @@ func (a *xorAppender) writeVDelta(v float64) { a.leading, a.trailing = xorWrite(a.b, v, a.v, a.leading, a.trailing) } -func xorWrite( - b *bstream, - current, previous float64, - currentLeading, currentTrailing uint8, -) (newLeading, newTrailing uint8) { - delta := math.Float64bits(current) ^ math.Float64bits(previous) - - if delta == 0 { - b.writeBit(zero) - return - } - b.writeBit(one) - - leading := uint8(bits.LeadingZeros64(delta)) - trailing := uint8(bits.TrailingZeros64(delta)) - - // Clamp number of leading zeros to avoid overflow when encoding. - if leading >= 32 { - leading = 31 - } - - if currentLeading != 0xff && leading >= currentLeading && trailing >= currentTrailing { - b.writeBit(zero) - b.writeBits(delta>>currentTrailing, 64-int(currentLeading)-int(currentTrailing)) - return currentLeading, currentTrailing - } - - b.writeBit(one) - b.writeBits(uint64(leading), 5) - - // Note that if leading == trailing == 0, then sigbits == 64. But that value doesn't actually fit into the 6 bits we have. - // Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0). - // So instead we write out a 0 and adjust it back to 64 on unpacking. - sigbits := 64 - leading - trailing - b.writeBits(uint64(sigbits), 6) - b.writeBits(delta>>trailing, int(sigbits)) - return leading, trailing -} - type xorIterator struct { br bstreamReader numTotal uint16 @@ -415,70 +376,117 @@ func (it *xorIterator) Next() bool { } func (it *xorIterator) readValue() bool { - bit, err := it.br.readBitFast() - if err != nil { - bit, err = it.br.readBit() - } + val, leading, trailing, err := xorRead(&it.br, it.val, it.leading, it.trailing) if err != nil { it.err = err return false } - - if bit == zero { - // it.val = it.val - } else { - bit, err := it.br.readBitFast() - if err != nil { - bit, err = it.br.readBit() - } - if err != nil { - it.err = err - return false - } - if bit == zero { - // reuse leading/trailing zero bits - // it.leading, it.trailing = it.leading, it.trailing - } else { - bits, err := it.br.readBitsFast(5) - if err != nil { - bits, err = it.br.readBits(5) - } - if err != nil { - it.err = err - return false - } - it.leading = uint8(bits) - - bits, err = it.br.readBitsFast(6) - if err != nil { - bits, err = it.br.readBits(6) - } - if err != nil { - it.err = err - return false - } - mbits := uint8(bits) - // 0 significant bits here means we overflowed and we actually need 64; see comment in encoder - if mbits == 0 { - mbits = 64 - } - it.trailing = 64 - it.leading - mbits - } - - mbits := 64 - it.leading - it.trailing - bits, err := it.br.readBitsFast(mbits) - if err != nil { - bits, err = it.br.readBits(mbits) - } - if err != nil { - it.err = err - return false - } - vbits := math.Float64bits(it.val) - vbits ^= bits << it.trailing - it.val = math.Float64frombits(vbits) - } - + it.val, it.leading, it.trailing = val, leading, trailing it.numRead++ return true } + +func xorWrite( + b *bstream, + current, previous float64, + currentLeading, currentTrailing uint8, +) (newLeading, newTrailing uint8) { + delta := math.Float64bits(current) ^ math.Float64bits(previous) + + if delta == 0 { + b.writeBit(zero) + return + } + b.writeBit(one) + + leading := uint8(bits.LeadingZeros64(delta)) + trailing := uint8(bits.TrailingZeros64(delta)) + + // Clamp number of leading zeros to avoid overflow when encoding. + if leading >= 32 { + leading = 31 + } + + if currentLeading != 0xff && leading >= currentLeading && trailing >= currentTrailing { + b.writeBit(zero) + b.writeBits(delta>>currentTrailing, 64-int(currentLeading)-int(currentTrailing)) + return currentLeading, currentTrailing + } + + b.writeBit(one) + b.writeBits(uint64(leading), 5) + + // Note that if leading == trailing == 0, then sigbits == 64. But that value doesn't actually fit into the 6 bits we have. + // Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0). + // So instead we write out a 0 and adjust it back to 64 on unpacking. + sigbits := 64 - leading - trailing + b.writeBits(uint64(sigbits), 6) + b.writeBits(delta>>trailing, int(sigbits)) + return leading, trailing +} + +func xorRead( + br *bstreamReader, currentValue float64, currentLeading, currentTrailing uint8, +) (newValue float64, newLeading, newTrailing uint8, err error) { + var bit bit + var bits uint64 + + bit, err = br.readBitFast() + if err != nil { + bit, err = br.readBit() + } + if err != nil { + return + } + if bit == zero { + return currentValue, currentLeading, currentTrailing, nil + } + bit, err = br.readBitFast() + if err != nil { + bit, err = br.readBit() + } + if err != nil { + return + } + if bit == zero { + // Reuse leading/trailing zero bits. + newLeading, newTrailing = currentLeading, currentTrailing + } else { + bits, err = br.readBitsFast(5) + if err != nil { + bits, err = br.readBits(5) + } + if err != nil { + return + } + newLeading = uint8(bits) + + bits, err = br.readBitsFast(6) + if err != nil { + bits, err = br.readBits(6) + } + if err != nil { + return + } + mbits := uint8(bits) + // 0 significant bits here means we overflowed and we actually + // need 64; see comment in xrWrite. + if mbits == 0 { + mbits = 64 + } + newTrailing = 64 - newLeading - mbits + } + + mbits := 64 - newLeading - newTrailing + bits, err = br.readBitsFast(mbits) + if err != nil { + bits, err = br.readBits(mbits) + } + if err != nil { + return + } + vbits := math.Float64bits(currentValue) + vbits ^= bits << newTrailing + newValue = math.Float64frombits(vbits) + return +} From 4998b9750f635e5a69b6f0548856fcde2bd3fe07 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Tue, 19 Oct 2021 15:28:24 +0200 Subject: [PATCH 3/3] chunkenc: Bugfix and naming tweaks Signed-off-by: beorn7 --- tsdb/chunkenc/xor.go | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/tsdb/chunkenc/xor.go b/tsdb/chunkenc/xor.go index d390806b31..9b52ed57a3 100644 --- a/tsdb/chunkenc/xor.go +++ b/tsdb/chunkenc/xor.go @@ -388,41 +388,44 @@ func (it *xorIterator) readValue() bool { func xorWrite( b *bstream, - current, previous float64, + newValue, currentValue float64, currentLeading, currentTrailing uint8, ) (newLeading, newTrailing uint8) { - delta := math.Float64bits(current) ^ math.Float64bits(previous) + delta := math.Float64bits(newValue) ^ math.Float64bits(currentValue) if delta == 0 { b.writeBit(zero) - return + return currentLeading, currentTrailing } b.writeBit(one) - leading := uint8(bits.LeadingZeros64(delta)) - trailing := uint8(bits.TrailingZeros64(delta)) + newLeading = uint8(bits.LeadingZeros64(delta)) + newTrailing = uint8(bits.TrailingZeros64(delta)) // Clamp number of leading zeros to avoid overflow when encoding. - if leading >= 32 { - leading = 31 + if newLeading >= 32 { + newLeading = 31 } - if currentLeading != 0xff && leading >= currentLeading && trailing >= currentTrailing { + if currentLeading != 0xff && newLeading >= currentLeading && newTrailing >= currentTrailing { + // In this case, we stick with the current leading/trailing. b.writeBit(zero) b.writeBits(delta>>currentTrailing, 64-int(currentLeading)-int(currentTrailing)) return currentLeading, currentTrailing } b.writeBit(one) - b.writeBits(uint64(leading), 5) + b.writeBits(uint64(newLeading), 5) - // Note that if leading == trailing == 0, then sigbits == 64. But that value doesn't actually fit into the 6 bits we have. - // Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0). - // So instead we write out a 0 and adjust it back to 64 on unpacking. - sigbits := 64 - leading - trailing + // Note that if newLeading == newTrailing == 0, then sigbits == 64. But + // that value doesn't actually fit into the 6 bits we have. Luckily, we + // never need to encode 0 significant bits, since that would put us in + // the other case (vdelta == 0). So instead we write out a 0 and adjust + // it back to 64 on unpacking. + sigbits := 64 - newLeading - newTrailing b.writeBits(uint64(sigbits), 6) - b.writeBits(delta>>trailing, int(sigbits)) - return leading, trailing + b.writeBits(delta>>newTrailing, int(sigbits)) + return } func xorRead(