Files
qb/chunkenc/insdelta.go
2026-05-31 20:01:28 +00:00

424 lines
11 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"
"gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/conbuf"
)
type ReverseInstantDeltaCompressor struct {
buf *conbuf.ContinuousBuffer
coef float64
pos int
baseValue float64
lastDelta int64
lastDeltaSize int
h byte
state *InstantDeltaBound
}
func NewReverseInstantDeltaCompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaCompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
s := &ReverseInstantDeltaCompressor{
buf: buf,
pos: size,
coef: coef,
}
if size > 0 {
i64, _, err := s.buf.GetVarInt64(0)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
s.baseValue = float64(i64) / s.coef
s.h = s.buf.GetByte(s.pos - 1)
s.lastDelta, s.lastDeltaSize, err = s.buf.ReverseGetVarInt64(s.pos - 2)
if err != nil {
log.Fatalf("bug: get last delta: %s", err)
}
}
return s
}
func (s *ReverseInstantDeltaCompressor) Size() int {
return s.pos
}
func (s *ReverseInstantDeltaCompressor) Append(value float64) {
if s.pos == 0 {
// base value
n := s.buf.PutVarInt64(s.pos, int64(value*s.coef))
s.pos += n
s.baseValue = value
s.appendNewLiteral(0)
} else {
tmp := (value - s.baseValue) * s.coef
if tmp > 0 {
tmp += eps
} else {
tmp -= eps
}
delta := int64(tmp)
if delta == s.lastDelta {
if s.h < 128 {
// run блок - отже треба збільшити лічильник
if s.h < 127 {
// increase counter
s.h++
s.buf.SetByte(s.pos-1, s.h)
} else {
// не можу збільшити - буде переповнення. Додаю новий literal блок
// counter overflow
// fix encode delta
s.appendNewLiteral(delta)
}
} else {
// literal блок.
// Якщо в ньому лише одне значення - перетворюю його на run блок.
// Інакше забираю останнє значення із literal блока і додаю новий run блок.
q := s.h & 127
if q == 0 { // 1 кодується як 0
s.convertLiteralToRun()
} else {
s.convertLastFromLiteralToRun()
}
}
} else {
if s.h < 127 {
// 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 *ReverseInstantDeltaCompressor) convertLastFromLiteralToRun() {
// Зменшую кількість елементів в literal блоці
s.h--
s.pos -= 1 + s.lastDeltaSize
s.buf.SetByte(s.pos, s.h) // закриваю literal блок
s.pos++
s.lastDeltaSize = s.buf.ReversePutVarInt64(s.pos, s.lastDelta)
s.pos += s.lastDeltaSize
s.h = 0 // run блок, довжини 2
s.buf.SetByte(s.pos, s.h)
s.pos++
}
func (s *ReverseInstantDeltaCompressor) convertLiteralToRun() {
// Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії.
s.h = 0
s.buf.SetByte(s.pos-1, s.h)
}
func (s *ReverseInstantDeltaCompressor) appendDeltaToLiteral(delta int64) {
s.h++ // збільшую к-сть дельт
s.lastDelta = delta
s.pos--
s.lastDeltaSize = s.buf.ReversePutVarInt64(s.pos, delta)
s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, s.h)
s.pos++
}
func (s *ReverseInstantDeltaCompressor) appendNewLiteral(delta int64) {
s.h = flagLiteral
s.lastDelta = delta
s.lastDeltaSize = s.buf.ReversePutVarInt64(s.pos, delta)
s.pos += s.lastDeltaSize
s.buf.SetByte(s.pos, flagLiteral) // literal, length = 1
s.pos++
}
func (s *ReverseInstantDeltaCompressor) DeleteLast() {}
type InstantDeltaBound struct {
Pos int
H byte
LastDelta int64
Chunks [][]byte
}
// delta h
func (s *ReverseInstantDeltaCompressor) Lock() {
if s.state != nil {
qb.Abort(qb.RepeatableLock, nil)
}
// позиція посувається вліво, отже може перескочити на попередній chunk
pos := s.pos - 1 - s.lastDeltaSize
chunksQty := pos / conbuf.ChunkSize
if (pos % conbuf.ChunkSize) > 0 {
chunksQty++
}
s.state = &InstantDeltaBound{
Pos: pos,
H: s.h,
LastDelta: s.lastDelta,
Chunks: s.buf.Chunks()[:chunksQty],
}
}
// fix - повернути в Pool буфери
func (s *ReverseInstantDeltaCompressor) Unlock() {
s.state = nil
}
func (s *ReverseInstantDeltaCompressor) Offset() int {
if s.state != nil {
return s.state.Pos
}
return 0
}
func (s *ReverseInstantDeltaCompressor) Snapshot() ([][]byte, int) {
if s.state == nil {
return s.buf.Chunks(), 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.ReversePutVarInt64(pos, s.state.LastDelta)
buf.SetByte(pos, s.state.H)
pos++
return chunks, pos
}
func (s *ReverseInstantDeltaCompressor) CreateDecompressor(fracDigits byte) qb.ValueDecompressor {
if s.state == nil {
d := NewReverseInstantDeltaDecompressor(s.buf, s.Size(), fracDigits)
d.RestoreFromEnd()
return d
}
d := NewReverseInstantDeltaDecompressor(s.buf, s.Size(), fracDigits)
d.RestoreFromBound(*s.state)
return d
}
func (s *ReverseInstantDeltaCompressor) Renew() {
// УВАГА!
// state не чіпаємо
s.buf = conbuf.New(nil)
s.pos = 0
//
s.baseValue = 0
s.lastDelta = 0
s.lastDeltaSize = 0
s.h = 0
}
func (s *ReverseInstantDeltaCompressor) CalcRequiredSpace(value float64) int {
return 0
}
func (s *ReverseInstantDeltaCompressor) Chunks() [][]byte {
return s.buf.Chunks()
}
// DECOMPRESSOR
type ReverseInstantDeltaDecompressor struct {
buf *conbuf.ContinuousBuffer
coef float64
pos int
bound int
baseValue float64
lastValue float64
isRun bool
pending int
done bool
}
func NewReverseInstantDeltaDecompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaDecompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
i64, n, err := buf.GetVarInt64(0)
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 &ReverseInstantDeltaDecompressor{
buf: buf,
coef: coef,
pos: size - 1, // last elem
baseValue: float64(i64) / coef,
bound: n,
}
}
func (s *ReverseInstantDeltaDecompressor) RestoreFromEnd() {
if s.pos > s.bound {
// читаю заголовок наступної серії
s.readHeader()
s.readValue()
} else {
s.done = true
}
}
func (s *ReverseInstantDeltaDecompressor) RestoreFromBound(bound InstantDeltaBound) {
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 *ReverseInstantDeltaDecompressor) 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 *ReverseInstantDeltaDecompressor) readHeader() {
h := s.buf.GetByte(s.pos)
s.pos--
s.decodeHeaderByte(h)
// fmt.Println("h:", h)
// fmt.Println("isRun:", s.isRun)
// fmt.Println("pending:", s.pending)
}
func (s *ReverseInstantDeltaDecompressor) readValue() {
i64, n, err := s.buf.ReverseGetVarInt64(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(i64)/s.coef
}
func (s *ReverseInstantDeltaDecompressor) decodeHeaderByte(h byte) {
s.isRun = h < 128
if s.isRun {
s.pending = int(h&127) + 2
} else {
s.pending = int(h&127) + 1
}
}
// DECOMPRESSOR
// type ReverseInstantDeltaDecompressor struct {
// buf *conbuf.ContinuousBuffer
// pos int
// bound int
// baseValue float64
// lastValue float64
// coef float64
// isRun bool
// pending int
// }
// func NewReverseInstantDeltaDecompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaDecompressor {
// var coef float64 = 1
// if fracDigits > 0 {
// coef = math.Pow(10, float64(fracDigits))
// }
// u64, n, err := buf.GetVarInt64(0)
// 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 &ReverseInstantDeltaDecompressor{
// buf: buf,
// coef: coef,
// pos: size - 1, // last elem
// baseValue: float64(u64) / coef,
// bound: n,
// }
// }
// func (s *ReverseInstantDeltaDecompressor) NextValue() (value float64, done bool) {
// //fmt.Printf("NextValue(): bound: %d, pos: %d, pending: %d\n", s.bound, s.pos, s.pending)
// if s.pending > 0 {
// // якщо в серії залишаються елементи
// if !s.isRun {
// s.readValue()
// }
// s.pending--
// return s.lastValue, false
// }
// if s.pos > s.bound {
// // читаю заголовок наступної серії
// s.readHeader()
// s.readValue()
// s.pending--
// return s.lastValue, false
// }
// // серія завершена - перевіряю чи є ще серії
// return 0, true
// }
// func (s *ReverseInstantDeltaDecompressor) readHeader() {
// h := s.buf.GetByte(s.pos)
// s.pos--
// s.isRun = h < 128
// if s.isRun {
// s.pending = int(h&127) + 2
// } else {
// s.pending = int(h&127) + 1
// }
// // fmt.Println("h:", h)
// // fmt.Println("isRun:", s.isRun)
// // fmt.Println("pending:", s.pending)
// }
// func (s *ReverseInstantDeltaDecompressor) readValue() {
// i64, n, err := s.buf.ReverseGetVarInt64(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(i64)/s.coef
// }