Files
qb/enc/value_delta.go
2026-06-11 01:46:25 +00:00

394 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package enc
import (
"io"
"log"
"math"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
)
const (
tmpValueSize = 19
)
type ValueDeltaCompressor struct {
buf []byte
coef float64
pos int // payload size
baseValue float64
lastDelta uint64
state *ValueDeltaCapturedState
}
// Після відновлення із снапшота
func NewValueDeltaCompressor(fracDigits byte, buf []byte, payloadSize int) *ValueDeltaCompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
s := &ValueDeltaCompressor{
buf: buf,
coef: coef,
pos: payloadSize,
}
if payloadSize > 0 {
// base value на початку
u64, _, err := bin.GetVarUint64(s.buf)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
s.baseValue = float64(u64) / s.coef
s.lastDelta, _, err = bin.ReverseGetVarUint64(s.buf[:s.pos-2]) // skip h byte
if err != nil {
log.Fatalf("bug: get last delta: %s", err)
}
}
return s
}
// можна виділити буфер максимального розміру, що може бути змінено під час кодування
// закодувати на етапі evaluate значення і повернути buf + offset. Причому буфер може бути
// спільний на всі metrics
// arr -
func (s *ValueDeltaCompressor) Evaluate(tmp []byte, value float64) qb.ValueEvaluationReport {
var (
delta uint64
offset int
i int
)
if s.pos > 0 {
delta = uint64((value-s.baseValue)*s.coef + eps)
h := s.buf[s.pos-1]
if h < 128 {
// run
if delta == s.lastDelta && h < 127 {
// incrementRun
tmp[i] = h + 1
i++
offset = 1 // перезапис h
} else {
// endSeries
n, _ := bin.ReversePutVarUint64(tmp, delta)
i += n
tmp[i] = 128 // start new literal (length=1)
i++
}
} else {
// literal
if delta != s.lastDelta {
if h < 255 {
// incrementLiteral
n, _ := bin.ReversePutVarUint64(tmp, delta)
i += n
tmp[i] = h + 1
i++
offset = 1 // перезапис h
} else {
// endSeries
n, _ := bin.ReversePutVarUint64(tmp, delta)
i += n
tmp[i] = 128 // start new literal (length=1)
i++
}
} else {
// startRun
if h > 128 {
tmp[i] = h - 1 // зменшую довжину попередньої серії на 1
i++
n, _ := bin.ReversePutVarUint64(tmp[i:], delta)
i += n
tmp[i] = 0 // start new run (length=2)
i++
offset = 1 + n // перезапис пари delta/h
} else {
tmp[i] = 0 // change literal (length=1) to run (length=2)
i++
offset = 1
}
}
}
} else {
n, _ := bin.PutVarUint64(tmp, uint64(value*s.coef)) // base value
i += n
n, _ = bin.ReversePutVarUint64(tmp[i:], 0) // delta
i += n
tmp[i] = 128 // start new literal (length=1)
i++
}
return qb.ValueEvaluationReport{
Offset: offset,
ChangeSize: i,
TotalSpace: s.pos + i - offset,
Delta: delta,
}
}
// pos завжди вказує на h
func (s *ValueDeltaCompressor) Append(offset int, change []byte, value float64, delta uint64) {
if s.pos > 0 {
s.lastDelta = delta
} else {
s.baseValue = value
}
copy(s.buf[s.pos-offset:], change)
s.pos += len(change) - offset
}
func (s *ValueDeltaCompressor) DeleteLast() {
}
type ValueDeltaCapturedState struct {
H byte
LastDelta uint64
Payload []byte
}
func (s *ValueDeltaCompressor) CaptureState() {
if s.state != nil {
qb.Abort(qb.RepeatableLock, nil)
}
// позиція посувається вліво, отже може перескочити на попередній chunk
pos := s.pos - 1 - bin.CountVarUint64(s.lastDelta)
s.state = &ValueDeltaCapturedState{
H: s.buf[s.pos-1],
LastDelta: s.lastDelta,
Payload: s.buf[:pos],
}
}
// fix - повернути в Pool буфери
func (s *ValueDeltaCompressor) ForgetCapturedState() {
s.state = nil
}
func (s *ValueDeltaCompressor) Size() int {
if s.state == nil {
return s.pos
} else {
return bin.CountVarUint64(s.state.LastDelta) + hSize + len(s.state.Payload)
}
}
// // Snapshot - для створення снапшота.
// func (s *ValueDeltaCompressor) Payload() []byte {
// if s.state == nil {
// return s.buf[:s.pos]
// } else {
// return s.state.Payload
// }
// }
func (s *ValueDeltaCompressor) WritePayloadTo(w io.Writer) (err error) {
if s.state == nil {
_, err = w.Write(s.buf[:s.pos])
return
} else {
_, err = w.Write(s.state.Payload)
if err != nil {
return
}
_, err = bin.WriteVarUint64(w, s.state.LastDelta)
if err != nil {
return
}
_, err = w.Write([]byte{
s.state.H,
})
return
}
}
func (s *ValueDeltaCompressor) Rotate(newbuf []byte) {
// УВАГА!
// state не чіпаємо
s.buf = newbuf
s.pos = 0
s.baseValue = 0
s.lastDelta = 0
}
func (s *ValueDeltaCompressor) LastValue() float64 {
var delta uint64
if s.state == nil {
delta = s.lastDelta
} else {
delta = s.state.LastDelta
}
return s.baseValue + float64(delta)*s.coef
}
func (s *ValueDeltaCompressor) CreateDecompressor() qb.ValueDecompressor {
if s.pos == 0 {
return nil
}
var (
h = s.buf[s.pos-1]
lastDelta = s.lastDelta
payload = s.buf[:s.pos]
)
if s.state != nil {
h = s.state.H
lastDelta = s.state.LastDelta
payload = s.state.Payload
}
return NewValueDeltaDecompressorFromState(ValueDeltaDecompressorFromStateOptions{
Coef: s.coef,
H: h,
LastDelta: lastDelta,
Payload: payload,
})
}
// DECOMPRESSOR
type ValueDeltaDecompressor struct {
buf []byte
coef float64
pos int
bound int
baseValue float64
lastValue float64
isRun bool
pending int
done bool
}
func NewValueDeltaDecompressor(buf []byte, fracDigits byte) *ValueDeltaDecompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
u64, n, err := bin.GetVarUint64(buf)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
//fmt.Printf("baseValue: %.2f, bound: %d, pos: %d\n", float64(u64)/coef, n, size)
s := &ValueDeltaDecompressor{
buf: buf,
coef: coef,
pos: len(buf), // first free
baseValue: float64(u64) / coef,
bound: n,
}
// читаю заголовок наступної серії
s.readHeader()
s.readValue()
return s
}
type ValueDeltaDecompressorFromStateOptions struct {
Coef float64
H byte
LastDelta uint64
Payload []byte
}
func NewValueDeltaDecompressorFromState(opt ValueDeltaDecompressorFromStateOptions) *ValueDeltaDecompressor {
u64, n, err := bin.GetVarUint64(opt.Payload)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
//fmt.Printf("baseValue: %.2f, bound: %d, pos: %d\n", float64(u64)/opt.Coef, n, size)
s := &ValueDeltaDecompressor{
buf: opt.Payload,
coef: opt.Coef,
pos: len(opt.Payload), // first free
baseValue: float64(u64) / opt.Coef,
bound: n,
}
s.lastValue = s.baseValue + float64(opt.LastDelta)/s.coef
s.decodeHeaderByte(opt.H)
//fmt.Printf("payload: %d\n", opt.Payload)
//fmt.Printf("restore from state: bound=%d, isRun=%t, pending=%d, baseValue=%v, lastValue=%v\n",
// n, s.isRun, s.pending, s.baseValue, s.lastValue)
return s
}
func (s *ValueDeltaDecompressor) NextValue() (value float64, done bool) {
//fmt.Printf("NextValue(): bound: %d, pos: %d, pending: %d\n", s.bound, s.pos, s.pending)
if s.done {
return 0, true
}
// метод працює як do while - спочатку значення, а потім перевірка умови
value = s.lastValue
s.pending--
if s.pending > 0 {
// якщо в серії залишаються елементи
if !s.isRun {
s.readValue()
}
} else if s.pos > s.bound {
// в серії більше немає елементів, отже перевіряє чи є ще дані в буфері.
// дані є - читаю заголовок наступної серії
s.readHeader()
s.readValue()
} else {
s.done = true
}
// серія завершена - перевіряю чи є ще серії
return value, false
}
func (s *ValueDeltaDecompressor) readHeader() {
//fmt.Println("read from pos:", s.pos)
s.pos--
h := s.buf[s.pos]
s.decodeHeaderByte(h)
// fmt.Println("h:", h)
// fmt.Println("isRun:", s.isRun)
// fmt.Println("pending:", s.pending)
}
func (s *ValueDeltaDecompressor) readValue() {
u64, n, err := bin.ReverseGetVarUint64(s.buf[:s.pos])
if err != nil {
log.Fatalln(err)
}
// fmt.Println()
// fmt.Println("read from pos:", s.pos)
// fmt.Println("read delta:", u64)
// fmt.Println("read delta n:", n)
// fmt.Println()
s.pos -= n
s.lastValue = s.baseValue + float64(u64)/s.coef
//fmt.Println(s.baseValue, float64(u64)/s.coef)
}
func (s *ValueDeltaDecompressor) decodeHeaderByte(h byte) {
s.isRun = h < 128
if s.isRun {
s.pending = int(h) + 2
} else {
s.pending = int(h&127) + 1
}
}
/*
Формат:
base value (var u64)
delta (var u64)
q (qty of previous deltas; msb=0 - series, msb=1 - non series, low 7 bits - qty 1..128)
delta
delta
delta
q
Дельта рахується від base value.
Декодування у зворотньому порядку.
Після base value слідують run або literal блоки.
Run блок - це delta + header byte в кінці.
Literal блок - це від одної до N дельт + header byte в кінці.
Спочатку створюється literal блок.
Якщо для останної дельти додається дублікат, literal блок модифікується -
лічильник зменшується до 1. А остання дельта переміщюється в новий run блок.
Причому лічильник 0 - означає 2 елементи. Приклад:
До:
v1 v2 v3 h-byte(literal, 3) <- v3
Після:
v1 v2 h-byte(literal, 2) v3 h-byte(run, 2)
*/