Files
qb/enc/cumdelta.go
2026-06-09 16:42:17 +00:00

465 lines
12 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"
)
// COMPRESSOR REVERSE
/*
Формат:
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)
*/
type CumulativeDeltaCompressor struct {
buf []byte
coef float64
pos int
baseValue float64
lastDelta uint64
h byte
state *CumulativeDeltaCapturedState
}
// Після відновлення із снапшота
func NewCumulativeDeltaCompressor(buf []byte, fracDigits byte) *CumulativeDeltaCompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
return &CumulativeDeltaCompressor{
buf: buf,
coef: coef,
}
}
func (s *CumulativeDeltaCompressor) RestoreState(payloadSize int) {
if payloadSize > 0 {
s.pos = payloadSize - 1 // завжди показує на h
// 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.h = s.buf[s.pos]
s.lastDelta, _, err = bin.ReverseGetVarUint64(s.buf[:s.pos-1])
if err != nil {
log.Fatalf("bug: get last delta: %s", err)
}
}
}
// func (s *CumulativeDeltaCompressor) Size() int {
// return s.pos
// }
func (s *CumulativeDeltaCompressor) Evaluate(value float64) (compressionWay int, requiredSpace int) {
delta := uint64((value-s.baseValue)*s.coef + eps) // fix - if delta 0, no eps
requiredSpace = s.pos
// fix - requiredSpace - all space
if s.pos > 0 {
if s.h < 128 {
// run
if delta == s.lastDelta && s.h < 127 {
compressionWay = incrementRun
} else {
compressionWay = endSeries
requiredSpace += bin.CountVarUint64(uint64(delta)) + hSize
}
} else {
// literal
if delta != s.lastDelta {
if s.h < 255 {
compressionWay = incrementLiteral
requiredSpace += bin.CountVarUint64(uint64(delta))
} else {
compressionWay = endSeries
requiredSpace += bin.CountVarUint64(uint64(delta)) + hSize
}
} else {
compressionWay = startRun
if s.h > 128 {
requiredSpace += hSize // h - end of run
}
}
}
} else {
// encode base value
compressionWay = addBaseValue
requiredSpace = bin.CountVarUint64(uint64(value*s.coef)) + // base value
bin.CountVarUint64(0) + // new delta
hSize // new h
}
return
}
// pos завжди вказує на h
func (s *CumulativeDeltaCompressor) Compress(compressionWay int, value float64) {
delta := uint64((value-s.baseValue)*s.coef + eps)
switch compressionWay {
case incrementRun:
s.h++
case incrementLiteral:
s.lastDelta = delta
s.h++
// write previous delta and increment counter
n, _ := bin.ReversePutVarUint64(s.buf[s.pos:], delta)
s.pos += n
case endSeries:
s.lastDelta = delta
n, _ := bin.ReversePutVarUint64(s.buf[s.pos:], delta)
s.pos += n
s.h = 128 // start new literal (length=1)
case startRun:
if s.h > 128 {
// write h - end of literal (because length > 1)
s.pos -= 1 + bin.CountVarUint64(s.lastDelta)
s.h--
s.buf[s.pos] = s.h
s.pos++
// run delta
n, _ := bin.ReversePutVarUint64(s.buf[s.pos:], delta)
s.pos += n
} else {
}
// start new run (length=2)
s.h = 0
case addBaseValue:
// start new literal (length=1)
n, _ := bin.PutVarUint64(s.buf[s.pos:], uint64(value*s.coef))
s.pos += n
s.baseValue = value
s.lastDelta = 0
n, _ = bin.ReversePutVarUint64(s.buf[s.pos:], s.lastDelta)
s.pos += n
// start new literal (length=1)
s.h = 128
}
s.buf[s.pos] = s.h
}
func (s *CumulativeDeltaCompressor) DeleteLast() {
}
type CumulativeDeltaCapturedState struct {
H byte
LastDelta uint64
Payload []byte
}
func (s *CumulativeDeltaCompressor) CaptureState() {
if s.state != nil {
qb.Abort(qb.RepeatableLock, nil)
}
// позиція посувається вліво, отже може перескочити на попередній chunk
pos := s.pos - hSize - bin.CountVarUint64(s.lastDelta)
s.state = &CumulativeDeltaCapturedState{
H: s.h,
LastDelta: s.lastDelta,
Payload: s.buf[:pos],
}
}
// fix - повернути в Pool буфери
func (s *CumulativeDeltaCompressor) ForgetCapturedState() {
s.state = nil
}
func (s *CumulativeDeltaCompressor) Offset() int {
// if s.state != nil {
// return s.state.Pos
// }
return 0
}
func (s *CumulativeDeltaCompressor) Size() int {
if s.state == nil {
return s.pos
} else {
return bin.CountVarUint64(s.state.LastDelta) + hSize + len(s.state.Payload)
}
}
// Snapshot - для створення снапшота.
func (s *CumulativeDeltaCompressor) Payload() []byte {
if s.state == nil {
return s.buf[:s.pos]
} else {
return s.state.Payload
}
}
func (s *CumulativeDeltaCompressor) 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 *CumulativeDeltaCompressor) encodeTail(lastDelta uint64, h byte) []byte {
// tail := make([]byte, 10) // max var uint64 + h
// n, _ := bin.PutVarUint64(tail, lastDelta)
// tail[n] = h
// return tail[:n+1]
// }
func (s *CumulativeDeltaCompressor) Rotate(newbuf []byte) {
// n, _ := bin.PutVarUint64(s.buf[s.pos:], s.lastDelta)
// s.pos += n
// s.buf[s.pos] = s.h
// s.pos++
// payloadSize = s.pos
// УВАГА!
// state не чіпаємо
s.buf = newbuf
s.pos = 0
s.baseValue = 0
s.lastDelta = 0
s.h = 0
}
func (s *CumulativeDeltaCompressor) LastValue() float64 {
if s.state == nil {
return s.baseValue + float64(s.lastDelta)*s.coef
} else {
return s.baseValue + float64(s.state.LastDelta)*s.coef
}
}
func (s *CumulativeDeltaCompressor) CreateDecompressor() qb.ValueDecompressor {
var (
h = s.h
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 NewCumulativeDeltaDecompressorFromState(CumulativeDeltaDecompressorFromStateOptions{
Coef: s.coef,
H: h,
LastDelta: lastDelta,
Payload: payload,
})
}
// DECOMPRESSOR
type CumulativeDeltaDecompressor struct {
buf []byte
coef float64
pos int
bound int
baseValue float64
lastValue float64
isRun bool
pending int
done bool
}
func NewCumulativeDeltaDecompressor(buf []byte, fracDigits byte) *CumulativeDeltaDecompressor {
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 := &CumulativeDeltaDecompressor{
buf: buf,
coef: coef,
pos: len(buf), // first free
baseValue: float64(u64) / coef,
bound: n,
}
// читаю заголовок наступної серії
s.readHeader()
s.readValue()
return s
}
type CumulativeDeltaDecompressorFromStateOptions struct {
Coef float64
H byte
LastDelta uint64
Payload []byte
}
func NewCumulativeDeltaDecompressorFromState(opt CumulativeDeltaDecompressorFromStateOptions) *CumulativeDeltaDecompressor {
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 := &CumulativeDeltaDecompressor{
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 *CumulativeDeltaDecompressor) 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 *CumulativeDeltaDecompressor) 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 *CumulativeDeltaDecompressor) 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 *CumulativeDeltaDecompressor) decodeHeaderByte(h byte) {
s.isRun = h < 128
if s.isRun {
s.pending = int(h) + 2
} else {
s.pending = int(h&127) + 1
}
}
//
// func (s *CumulativeDeltaCompressor) Append(value float64) {
// if s.pos == 0 {
// // base value
// n, _ := bin.PutVarUint64(s.buf[s.pos:], uint64(value*s.coef))
// s.pos += n
// s.baseValue = value
// s.appendNewLiteral(0)
// } else {
// delta := uint64((value-s.baseValue)*s.coef + eps)
// if delta == s.lastDelta {
// if s.h < 128 {
// // run блок - отже треба збільшити лічильник
// if s.h < 127 {
// // increase counter
// s.h++
// s.buf[s.pos-1] = s.h
// } else {
// // не можу збільшити - буде переповнення. Додаю новий literal блок
// // counter overflow
// s.appendNewLiteral(delta)
// }
// } else {
// // literal блок.
// // Якщо в ньому лише одне значення - перетворюю його на run блок.
// // Інакше забираю останнє значення із literal блока і додаю новий run блок.
// q := s.h & 127
// if q == 0 { // 1 кодується як 0
// s.convertLiteralToRun()
// } else {
// // забираю останнє значення із literal блоку щоб зробити run блок
// s.convertLastFromLiteralToRun()
// }
// }
// } else {
// if s.h < 127 { // fix 128?
// // end of run
// s.appendNewLiteral(delta)
// } else {
// if s.h < 255 {
// // encode value from pos - 1, then append h byte
// s.appendDeltaToLiteral(delta)
// } else {
// // overflowed - encode new
// s.appendNewLiteral(delta)
// }
// }
// }
// }
// }