Files
qb/chunkenc/cumdelta.go
2026-06-06 15:36:27 +03:00

367 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 chunkenc
import (
"fmt"
"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 ReverseCumulativeDeltaCompressor struct {
buf []byte
coef float64
pos int
baseValue float64
lastDelta uint64
lastDeltaSize int
h byte
state *CumulativeDeltaBound
}
func NewReverseCumulativeDeltaCompressor(buf []byte, size int, fracDigits byte) *ReverseCumulativeDeltaCompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
s := &ReverseCumulativeDeltaCompressor{
buf: buf,
pos: size, // перший вільний байт
coef: coef,
}
if size > 0 {
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-1]
s.lastDelta, s.lastDeltaSize, err = bin.ReverseGetVarUint64(s.buf[:s.pos-2])
if err != nil {
log.Fatalf("bug: get last delta: %s", err)
}
}
return s
}
func (s *ReverseCumulativeDeltaCompressor) Size() int {
return s.pos
}
func (s *ReverseCumulativeDeltaCompressor) CalcRequiredSpace(value float64) int {
return 0
}
func (s *ReverseCumulativeDeltaCompressor) 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)
}
}
}
}
}
func (s *ReverseCumulativeDeltaCompressor) convertLastFromLiteralToRun() {
// Зменшую кількість елементів в literal блоці
s.h--
s.pos -= 1 + s.lastDeltaSize
s.buf[s.pos] = s.h // закриваю literal блок
s.pos++
s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], s.lastDelta)
s.pos += s.lastDeltaSize
s.h = 0 // run блок, довжини 2
s.buf[s.pos] = s.h
s.pos++
}
func (s *ReverseCumulativeDeltaCompressor) convertLiteralToRun() {
// Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії.
s.h = 0
s.buf[s.pos-1] = s.h
}
func (s *ReverseCumulativeDeltaCompressor) appendDeltaToLiteral(delta uint64) {
s.h++ // збільшую к-сть дельт
s.lastDelta = delta
s.pos--
s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], delta)
s.pos += s.lastDeltaSize
s.buf[s.pos] = s.h
s.pos++
}
func (s *ReverseCumulativeDeltaCompressor) appendNewLiteral(delta uint64) {
s.h = flagLiteral
s.lastDelta = delta
s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], delta)
s.pos += s.lastDeltaSize
s.buf[s.pos] = flagLiteral // literal, length = 1
s.pos++
}
func (s *ReverseCumulativeDeltaCompressor) DeleteLast() {
}
type CumulativeDeltaBound struct {
Pos int
H byte
LastDelta uint64
Chunks []byte
}
// delta h
func (s *ReverseCumulativeDeltaCompressor) Lock() {
if s.state != nil {
qb.Abort(qb.RepeatableLock, nil)
}
// позиція посувається вліво, отже може перескочити на попередній chunk
pos := s.pos - 1 - s.lastDeltaSize
s.state = &CumulativeDeltaBound{
Pos: pos,
H: s.h,
LastDelta: s.lastDelta,
Chunks: s.buf[:s.pos], // fix check ?
}
}
// fix - повернути в Pool буфери
func (s *ReverseCumulativeDeltaCompressor) Unlock() {
s.state = nil
}
func (s *ReverseCumulativeDeltaCompressor) Offset() int {
if s.state != nil {
return s.state.Pos
}
return 0
}
func (s *ReverseCumulativeDeltaCompressor) Snapshot() ([]byte, int) {
// if s.state == nil {
// return s.buf, s.Size()
// }
// // ВАЖЛИВО!
// // Треба відтворити стан останнього чанка
// var (
// pos = s.state.Pos
// chunk = make([]byte, conbuf.ChunkSize)
// lastChunkIdx = len(s.state.Chunks) - 1
// qtyToCopy = pos % conbuf.ChunkSize
// )
// copy(chunk, s.state.Chunks[lastChunkIdx][:qtyToCopy])
// chunks := append(s.state.Chunks[:lastChunkIdx], chunk)
// buf := conbuf.New(chunks)
// pos += buf.ReversePutVarUint64(pos, s.state.LastDelta)
// buf.SetByte(pos, s.state.H)
// pos++
// return chunks, pos
return nil, 0
}
func (s *ReverseCumulativeDeltaCompressor) CreateDecompressor(fracDigits byte) qb.ValueDecompressor {
if s.state == nil {
d := NewReverseCumulativeDeltaDecompressor(s.buf, s.Size(), fracDigits)
d.RestoreFromEnd()
return d
}
d := NewReverseCumulativeDeltaDecompressor(s.buf, s.Size(), fracDigits)
d.RestoreFromBound(*s.state)
return d
}
func (s *ReverseCumulativeDeltaCompressor) Renew() {
// УВАГА!
// state не чіпаємо
s.buf = make([]byte, minBufferSize)
s.pos = 0
//
s.baseValue = 0
s.lastDelta = 0
s.lastDeltaSize = 0
s.h = 0
}
func (s *ReverseCumulativeDeltaCompressor) Chunks() []byte {
return s.buf
}
// DECOMPRESSOR
type ReverseCumulativeDeltaDecompressor struct {
buf []byte
coef float64
pos int
bound int
baseValue float64
lastValue float64
isRun bool
pending int
done bool
}
func NewReverseCumulativeDeltaDecompressor(buf []byte, size int, fracDigits byte) *ReverseCumulativeDeltaDecompressor {
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)
return &ReverseCumulativeDeltaDecompressor{
buf: buf,
coef: coef,
pos: size - 1, // last elem
baseValue: float64(u64) / coef,
bound: n,
}
}
func (s *ReverseCumulativeDeltaDecompressor) RestoreFromEnd() {
if s.pos > s.bound {
// читаю заголовок наступної серії
s.readHeader()
s.readValue()
} else {
s.done = true
}
}
func (s *ReverseCumulativeDeltaDecompressor) RestoreFromBound(bound CumulativeDeltaBound) {
s.pos = bound.Pos - 1
s.lastValue = s.baseValue + float64(bound.LastDelta)
s.decodeHeaderByte(bound.H)
fmt.Printf("restore from bound: isRun=%t, pending=%d\n", s.isRun, s.pending)
}
func (s *ReverseCumulativeDeltaDecompressor) 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
}
// повертаю значення, що було прочитано в методі RestoreFromBound/RestoreFromEnd
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 *ReverseCumulativeDeltaDecompressor) readHeader() {
h := s.buf[s.pos]
s.pos--
s.decodeHeaderByte(h)
// fmt.Println("h:", h)
// fmt.Println("isRun:", s.isRun)
// fmt.Println("pending:", s.pending)
}
func (s *ReverseCumulativeDeltaDecompressor) 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
}
func (s *ReverseCumulativeDeltaDecompressor) decodeHeaderByte(h byte) {
s.isRun = h < 128
if s.isRun {
s.pending = int(h&127) + 2
} else {
s.pending = int(h&127) + 1
}
}
//