2022-07-03 11:08:30 +01:00
|
|
|
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
|
|
|
// Copyright (c) 2019 Klaus Post. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package s2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2023-03-30 22:39:55 +01:00
|
|
|
"math"
|
2022-07-03 11:08:30 +01:00
|
|
|
"math/bits"
|
|
|
|
)
|
|
|
|
|
|
|
|
// encodeBlockBest encodes a non-empty src to a guaranteed-large-enough dst. It
|
|
|
|
// assumes that the varint-encoded length of the decompressed bytes has already
|
|
|
|
// been written.
|
|
|
|
//
|
|
|
|
// It also assumes that:
|
2023-02-27 09:21:58 +00:00
|
|
|
//
|
2022-07-03 11:08:30 +01:00
|
|
|
// len(dst) >= MaxEncodedLen(len(src)) &&
|
2023-02-27 09:21:58 +00:00
|
|
|
// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
|
2023-03-30 22:39:55 +01:00
|
|
|
func encodeBlockBest(dst, src []byte, dict *Dict) (d int) {
|
2022-07-03 11:08:30 +01:00
|
|
|
// Initialize the hash tables.
|
|
|
|
const (
|
|
|
|
// Long hash matches.
|
|
|
|
lTableBits = 19
|
|
|
|
maxLTableSize = 1 << lTableBits
|
|
|
|
|
|
|
|
// Short hash matches.
|
|
|
|
sTableBits = 16
|
|
|
|
maxSTableSize = 1 << sTableBits
|
|
|
|
|
|
|
|
inputMargin = 8 + 2
|
2023-03-30 22:39:55 +01:00
|
|
|
|
|
|
|
debug = false
|
2022-07-03 11:08:30 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// sLimit is when to stop looking for offset/length copies. The inputMargin
|
|
|
|
// lets us use a fast path for emitLiteral in the main loop, while we are
|
|
|
|
// looking for copies.
|
|
|
|
sLimit := len(src) - inputMargin
|
|
|
|
if len(src) < minNonLiteralBlockSize {
|
|
|
|
return 0
|
|
|
|
}
|
2023-03-30 22:39:55 +01:00
|
|
|
sLimitDict := len(src) - inputMargin
|
|
|
|
if sLimitDict > MaxDictSrcOffset-inputMargin {
|
|
|
|
sLimitDict = MaxDictSrcOffset - inputMargin
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
|
|
|
|
var lTable [maxLTableSize]uint64
|
|
|
|
var sTable [maxSTableSize]uint64
|
|
|
|
|
|
|
|
// Bail if we can't compress to at least this.
|
|
|
|
dstLimit := len(src) - 5
|
|
|
|
|
|
|
|
// nextEmit is where in src the next emitLiteral should start from.
|
|
|
|
nextEmit := 0
|
|
|
|
|
|
|
|
// The encoded form must start with a literal, as there are no previous
|
|
|
|
// bytes to copy, so we start looking for hash matches at s == 1.
|
|
|
|
s := 1
|
2023-03-30 22:39:55 +01:00
|
|
|
repeat := 1
|
|
|
|
if dict != nil {
|
|
|
|
dict.initBest()
|
|
|
|
s = 0
|
|
|
|
repeat = len(dict.dict) - dict.repeat
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
cv := load64(src, s)
|
|
|
|
|
|
|
|
// We search for a repeat at -1, but don't output repeats when nextEmit == 0
|
|
|
|
const lowbitMask = 0xffffffff
|
|
|
|
getCur := func(x uint64) int {
|
|
|
|
return int(x & lowbitMask)
|
|
|
|
}
|
|
|
|
getPrev := func(x uint64) int {
|
|
|
|
return int(x >> 32)
|
|
|
|
}
|
|
|
|
const maxSkip = 64
|
|
|
|
|
|
|
|
for {
|
|
|
|
type match struct {
|
2023-03-30 22:39:55 +01:00
|
|
|
offset int
|
|
|
|
s int
|
|
|
|
length int
|
|
|
|
score int
|
|
|
|
rep, dict bool
|
2022-07-03 11:08:30 +01:00
|
|
|
}
|
|
|
|
var best match
|
|
|
|
for {
|
|
|
|
// Next src position to check
|
|
|
|
nextS := (s-nextEmit)>>8 + 1
|
|
|
|
if nextS > maxSkip {
|
|
|
|
nextS = s + maxSkip
|
|
|
|
} else {
|
|
|
|
nextS += s
|
|
|
|
}
|
|
|
|
if nextS > sLimit {
|
|
|
|
goto emitRemainder
|
|
|
|
}
|
2023-03-30 22:39:55 +01:00
|
|
|
if dict != nil && s >= MaxDictSrcOffset {
|
|
|
|
dict = nil
|
|
|
|
if repeat > s {
|
|
|
|
repeat = math.MinInt32
|
|
|
|
}
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
hashL := hash8(cv, lTableBits)
|
|
|
|
hashS := hash4(cv, sTableBits)
|
|
|
|
candidateL := lTable[hashL]
|
|
|
|
candidateS := sTable[hashS]
|
|
|
|
|
|
|
|
score := func(m match) int {
|
|
|
|
// Matches that are longer forward are penalized since we must emit it as a literal.
|
|
|
|
score := m.length - m.s
|
|
|
|
if nextEmit == m.s {
|
|
|
|
// If we do not have to emit literals, we save 1 byte
|
|
|
|
score++
|
|
|
|
}
|
|
|
|
offset := m.s - m.offset
|
|
|
|
if m.rep {
|
|
|
|
return score - emitRepeatSize(offset, m.length)
|
|
|
|
}
|
|
|
|
return score - emitCopySize(offset, m.length)
|
|
|
|
}
|
|
|
|
|
|
|
|
matchAt := func(offset, s int, first uint32, rep bool) match {
|
|
|
|
if best.length != 0 && best.s-best.offset == s-offset {
|
|
|
|
// Don't retest if we have the same offset.
|
|
|
|
return match{offset: offset, s: s}
|
|
|
|
}
|
|
|
|
if load32(src, offset) != first {
|
|
|
|
return match{offset: offset, s: s}
|
|
|
|
}
|
|
|
|
m := match{offset: offset, s: s, length: 4 + offset, rep: rep}
|
|
|
|
s += 4
|
2023-03-30 22:39:55 +01:00
|
|
|
for s < len(src) {
|
|
|
|
if len(src)-s < 8 {
|
|
|
|
if src[s] == src[m.length] {
|
|
|
|
m.length++
|
|
|
|
s++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
if diff := load64(src, s) ^ load64(src, m.length); diff != 0 {
|
|
|
|
m.length += bits.TrailingZeros64(diff) >> 3
|
|
|
|
break
|
|
|
|
}
|
|
|
|
s += 8
|
|
|
|
m.length += 8
|
|
|
|
}
|
|
|
|
m.length -= offset
|
|
|
|
m.score = score(m)
|
|
|
|
if m.score <= -m.s {
|
|
|
|
// Eliminate if no savings, we might find a better one.
|
|
|
|
m.length = 0
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
2023-03-30 22:39:55 +01:00
|
|
|
matchDict := func(candidate, s int, first uint32, rep bool) match {
|
|
|
|
// Calculate offset as if in continuous array with s
|
|
|
|
offset := -len(dict.dict) + candidate
|
|
|
|
if best.length != 0 && best.s-best.offset == s-offset && !rep {
|
|
|
|
// Don't retest if we have the same offset.
|
|
|
|
return match{offset: offset, s: s}
|
|
|
|
}
|
|
|
|
|
|
|
|
if load32(dict.dict, candidate) != first {
|
|
|
|
return match{offset: offset, s: s}
|
|
|
|
}
|
|
|
|
m := match{offset: offset, s: s, length: 4 + candidate, rep: rep, dict: true}
|
|
|
|
s += 4
|
|
|
|
if !rep {
|
|
|
|
for s < sLimitDict && m.length < len(dict.dict) {
|
|
|
|
if len(src)-s < 8 || len(dict.dict)-m.length < 8 {
|
|
|
|
if src[s] == dict.dict[m.length] {
|
|
|
|
m.length++
|
|
|
|
s++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if diff := load64(src, s) ^ load64(dict.dict, m.length); diff != 0 {
|
|
|
|
m.length += bits.TrailingZeros64(diff) >> 3
|
|
|
|
break
|
|
|
|
}
|
|
|
|
s += 8
|
|
|
|
m.length += 8
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for s < len(src) && m.length < len(dict.dict) {
|
|
|
|
if len(src)-s < 8 || len(dict.dict)-m.length < 8 {
|
|
|
|
if src[s] == dict.dict[m.length] {
|
|
|
|
m.length++
|
|
|
|
s++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if diff := load64(src, s) ^ load64(dict.dict, m.length); diff != 0 {
|
|
|
|
m.length += bits.TrailingZeros64(diff) >> 3
|
|
|
|
break
|
|
|
|
}
|
|
|
|
s += 8
|
|
|
|
m.length += 8
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m.length -= candidate
|
|
|
|
m.score = score(m)
|
|
|
|
if m.score <= -m.s {
|
|
|
|
// Eliminate if no savings, we might find a better one.
|
|
|
|
m.length = 0
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
|
|
|
|
bestOf := func(a, b match) match {
|
|
|
|
if b.length == 0 {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
if a.length == 0 {
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
as := a.score + b.s
|
|
|
|
bs := b.score + a.s
|
|
|
|
if as >= bs {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2023-03-30 22:39:55 +01:00
|
|
|
if s > 0 {
|
|
|
|
best = bestOf(matchAt(getCur(candidateL), s, uint32(cv), false), matchAt(getPrev(candidateL), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getCur(candidateS), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getPrev(candidateS), s, uint32(cv), false))
|
|
|
|
}
|
|
|
|
if dict != nil {
|
|
|
|
candidateL := dict.bestTableLong[hashL]
|
|
|
|
candidateS := dict.bestTableShort[hashS]
|
|
|
|
best = bestOf(best, matchDict(int(candidateL&0xffff), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchDict(int(candidateL>>16), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchDict(int(candidateS&0xffff), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchDict(int(candidateS>>16), s, uint32(cv), false))
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
{
|
2023-03-30 22:39:55 +01:00
|
|
|
if (dict == nil || repeat <= s) && repeat > 0 {
|
|
|
|
best = bestOf(best, matchAt(s-repeat+1, s+1, uint32(cv>>8), true))
|
|
|
|
} else if s-repeat < -4 && dict != nil {
|
|
|
|
candidate := len(dict.dict) - (repeat - s)
|
|
|
|
best = bestOf(best, matchDict(candidate, s, uint32(cv), true))
|
|
|
|
candidate++
|
|
|
|
best = bestOf(best, matchDict(candidate, s+1, uint32(cv>>8), true))
|
|
|
|
}
|
|
|
|
|
2022-07-03 11:08:30 +01:00
|
|
|
if best.length > 0 {
|
2023-03-30 22:39:55 +01:00
|
|
|
hashS := hash4(cv>>8, sTableBits)
|
2022-07-03 11:08:30 +01:00
|
|
|
// s+1
|
2023-03-30 22:39:55 +01:00
|
|
|
nextShort := sTable[hashS]
|
2022-07-03 11:08:30 +01:00
|
|
|
s := s + 1
|
|
|
|
cv := load64(src, s)
|
2023-03-30 22:39:55 +01:00
|
|
|
hashL := hash8(cv, lTableBits)
|
|
|
|
nextLong := lTable[hashL]
|
2022-07-03 11:08:30 +01:00
|
|
|
best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv), false))
|
2023-03-30 22:39:55 +01:00
|
|
|
|
|
|
|
// Dict at + 1
|
|
|
|
if dict != nil {
|
|
|
|
candidateL := dict.bestTableLong[hashL]
|
|
|
|
candidateS := dict.bestTableShort[hashS]
|
|
|
|
|
|
|
|
best = bestOf(best, matchDict(int(candidateL&0xffff), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchDict(int(candidateS&0xffff), s, uint32(cv), false))
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
|
|
|
|
// s+2
|
|
|
|
if true {
|
2023-03-30 22:39:55 +01:00
|
|
|
hashS := hash4(cv>>8, sTableBits)
|
|
|
|
|
|
|
|
nextShort = sTable[hashS]
|
2022-07-03 11:08:30 +01:00
|
|
|
s++
|
|
|
|
cv = load64(src, s)
|
2023-03-30 22:39:55 +01:00
|
|
|
hashL := hash8(cv, lTableBits)
|
|
|
|
nextLong = lTable[hashL]
|
|
|
|
|
|
|
|
if (dict == nil || repeat <= s) && repeat > 0 {
|
|
|
|
// Repeat at + 2
|
|
|
|
best = bestOf(best, matchAt(s-repeat, s, uint32(cv), true))
|
|
|
|
} else if repeat-s > 4 && dict != nil {
|
|
|
|
candidate := len(dict.dict) - (repeat - s)
|
|
|
|
best = bestOf(best, matchDict(candidate, s, uint32(cv), true))
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv), false))
|
2023-03-30 22:39:55 +01:00
|
|
|
|
|
|
|
// Dict at +2
|
|
|
|
// Very small gain
|
|
|
|
if dict != nil {
|
|
|
|
candidateL := dict.bestTableLong[hashL]
|
|
|
|
candidateS := dict.bestTableShort[hashS]
|
|
|
|
|
|
|
|
best = bestOf(best, matchDict(int(candidateL&0xffff), s, uint32(cv), false))
|
|
|
|
best = bestOf(best, matchDict(int(candidateS&0xffff), s, uint32(cv), false))
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
}
|
|
|
|
// Search for a match at best match end, see if that is better.
|
2023-02-27 09:21:58 +00:00
|
|
|
// Allow some bytes at the beginning to mismatch.
|
|
|
|
// Sweet spot is around 1-2 bytes, but depends on input.
|
|
|
|
// The skipped bytes are tested in Extend backwards,
|
|
|
|
// and still picked up as part of the match if they do.
|
|
|
|
const skipBeginning = 2
|
|
|
|
const skipEnd = 1
|
|
|
|
if sAt := best.s + best.length - skipEnd; sAt < sLimit {
|
|
|
|
|
|
|
|
sBack := best.s + skipBeginning - skipEnd
|
|
|
|
backL := best.length - skipBeginning
|
2022-07-03 11:08:30 +01:00
|
|
|
// Load initial values
|
|
|
|
cv = load64(src, sBack)
|
2023-02-27 09:21:58 +00:00
|
|
|
|
|
|
|
// Grab candidates...
|
2022-07-03 11:08:30 +01:00
|
|
|
next := lTable[hash8(load64(src, sAt), lTableBits)]
|
|
|
|
|
|
|
|
if checkAt := getCur(next) - backL; checkAt > 0 {
|
|
|
|
best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false))
|
|
|
|
}
|
|
|
|
if checkAt := getPrev(next) - backL; checkAt > 0 {
|
|
|
|
best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false))
|
|
|
|
}
|
2023-02-27 09:21:58 +00:00
|
|
|
// Disabled: Extremely small gain
|
|
|
|
if false {
|
|
|
|
next = sTable[hash4(load64(src, sAt), sTableBits)]
|
|
|
|
if checkAt := getCur(next) - backL; checkAt > 0 {
|
|
|
|
best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false))
|
|
|
|
}
|
|
|
|
if checkAt := getPrev(next) - backL; checkAt > 0 {
|
|
|
|
best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false))
|
|
|
|
}
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update table
|
|
|
|
lTable[hashL] = uint64(s) | candidateL<<32
|
|
|
|
sTable[hashS] = uint64(s) | candidateS<<32
|
|
|
|
|
|
|
|
if best.length > 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
cv = load64(src, nextS)
|
|
|
|
s = nextS
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extend backwards, not needed for repeats...
|
|
|
|
s = best.s
|
2023-03-30 22:39:55 +01:00
|
|
|
if !best.rep && !best.dict {
|
2022-07-03 11:08:30 +01:00
|
|
|
for best.offset > 0 && s > nextEmit && src[best.offset-1] == src[s-1] {
|
|
|
|
best.offset--
|
|
|
|
best.length++
|
|
|
|
s--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if false && best.offset >= s {
|
|
|
|
panic(fmt.Errorf("t %d >= s %d", best.offset, s))
|
|
|
|
}
|
|
|
|
// Bail if we exceed the maximum size.
|
|
|
|
if d+(s-nextEmit) > dstLimit {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
base := s
|
|
|
|
offset := s - best.offset
|
|
|
|
s += best.length
|
|
|
|
|
|
|
|
if offset > 65535 && s-base <= 5 && !best.rep {
|
|
|
|
// Bail if the match is equal or worse to the encoding.
|
|
|
|
s = best.s + 1
|
|
|
|
if s >= sLimit {
|
|
|
|
goto emitRemainder
|
|
|
|
}
|
|
|
|
cv = load64(src, s)
|
|
|
|
continue
|
|
|
|
}
|
2023-03-30 22:39:55 +01:00
|
|
|
if debug && nextEmit != base {
|
|
|
|
fmt.Println("EMIT", base-nextEmit, "literals. base-after:", base)
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
d += emitLiteral(dst[d:], src[nextEmit:base])
|
|
|
|
if best.rep {
|
2023-03-30 22:39:55 +01:00
|
|
|
if nextEmit > 0 || best.dict {
|
|
|
|
if debug {
|
|
|
|
fmt.Println("REPEAT, length", best.length, "offset:", offset, "s-after:", s, "dict:", best.dict, "best:", best)
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
// same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset.
|
|
|
|
d += emitRepeat(dst[d:], offset, best.length)
|
|
|
|
} else {
|
2023-03-30 22:39:55 +01:00
|
|
|
// First match without dict cannot be a repeat.
|
|
|
|
if debug {
|
|
|
|
fmt.Println("COPY, length", best.length, "offset:", offset, "s-after:", s, "dict:", best.dict, "best:", best)
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
d += emitCopy(dst[d:], offset, best.length)
|
|
|
|
}
|
|
|
|
} else {
|
2023-03-30 22:39:55 +01:00
|
|
|
if debug {
|
|
|
|
fmt.Println("COPY, length", best.length, "offset:", offset, "s-after:", s, "dict:", best.dict, "best:", best)
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
d += emitCopy(dst[d:], offset, best.length)
|
|
|
|
}
|
|
|
|
repeat = offset
|
|
|
|
|
|
|
|
nextEmit = s
|
|
|
|
if s >= sLimit {
|
|
|
|
goto emitRemainder
|
|
|
|
}
|
|
|
|
|
|
|
|
if d > dstLimit {
|
|
|
|
// Do we have space for more, if not bail.
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
// Fill tables...
|
|
|
|
for i := best.s + 1; i < s; i++ {
|
|
|
|
cv0 := load64(src, i)
|
|
|
|
long0 := hash8(cv0, lTableBits)
|
|
|
|
short0 := hash4(cv0, sTableBits)
|
|
|
|
lTable[long0] = uint64(i) | lTable[long0]<<32
|
|
|
|
sTable[short0] = uint64(i) | sTable[short0]<<32
|
|
|
|
}
|
|
|
|
cv = load64(src, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
emitRemainder:
|
|
|
|
if nextEmit < len(src) {
|
|
|
|
// Bail if we exceed the maximum size.
|
|
|
|
if d+len(src)-nextEmit > dstLimit {
|
|
|
|
return 0
|
|
|
|
}
|
2023-03-30 22:39:55 +01:00
|
|
|
if debug && nextEmit != s {
|
|
|
|
fmt.Println("emitted ", len(src)-nextEmit, "literals")
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
d += emitLiteral(dst[d:], src[nextEmit:])
|
|
|
|
}
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
// encodeBlockBestSnappy encodes a non-empty src to a guaranteed-large-enough dst. It
|
|
|
|
// assumes that the varint-encoded length of the decompressed bytes has already
|
|
|
|
// been written.
|
|
|
|
//
|
|
|
|
// It also assumes that:
|
2023-02-27 09:21:58 +00:00
|
|
|
//
|
2022-07-03 11:08:30 +01:00
|
|
|
// len(dst) >= MaxEncodedLen(len(src)) &&
|
2023-02-27 09:21:58 +00:00
|
|
|
// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
|
2022-07-03 11:08:30 +01:00
|
|
|
func encodeBlockBestSnappy(dst, src []byte) (d int) {
|
|
|
|
// Initialize the hash tables.
|
|
|
|
const (
|
|
|
|
// Long hash matches.
|
|
|
|
lTableBits = 19
|
|
|
|
maxLTableSize = 1 << lTableBits
|
|
|
|
|
|
|
|
// Short hash matches.
|
|
|
|
sTableBits = 16
|
|
|
|
maxSTableSize = 1 << sTableBits
|
|
|
|
|
|
|
|
inputMargin = 8 + 2
|
|
|
|
)
|
|
|
|
|
|
|
|
// sLimit is when to stop looking for offset/length copies. The inputMargin
|
|
|
|
// lets us use a fast path for emitLiteral in the main loop, while we are
|
|
|
|
// looking for copies.
|
|
|
|
sLimit := len(src) - inputMargin
|
|
|
|
if len(src) < minNonLiteralBlockSize {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
var lTable [maxLTableSize]uint64
|
|
|
|
var sTable [maxSTableSize]uint64
|
|
|
|
|
|
|
|
// Bail if we can't compress to at least this.
|
|
|
|
dstLimit := len(src) - 5
|
|
|
|
|
|
|
|
// nextEmit is where in src the next emitLiteral should start from.
|
|
|
|
nextEmit := 0
|
|
|
|
|
|
|
|
// The encoded form must start with a literal, as there are no previous
|
|
|
|
// bytes to copy, so we start looking for hash matches at s == 1.
|
|
|
|
s := 1
|
|
|
|
cv := load64(src, s)
|
|
|
|
|
|
|
|
// We search for a repeat at -1, but don't output repeats when nextEmit == 0
|
|
|
|
repeat := 1
|
|
|
|
const lowbitMask = 0xffffffff
|
|
|
|
getCur := func(x uint64) int {
|
|
|
|
return int(x & lowbitMask)
|
|
|
|
}
|
|
|
|
getPrev := func(x uint64) int {
|
|
|
|
return int(x >> 32)
|
|
|
|
}
|
|
|
|
const maxSkip = 64
|
|
|
|
|
|
|
|
for {
|
|
|
|
type match struct {
|
|
|
|
offset int
|
|
|
|
s int
|
|
|
|
length int
|
|
|
|
score int
|
|
|
|
}
|
|
|
|
var best match
|
|
|
|
for {
|
|
|
|
// Next src position to check
|
|
|
|
nextS := (s-nextEmit)>>8 + 1
|
|
|
|
if nextS > maxSkip {
|
|
|
|
nextS = s + maxSkip
|
|
|
|
} else {
|
|
|
|
nextS += s
|
|
|
|
}
|
|
|
|
if nextS > sLimit {
|
|
|
|
goto emitRemainder
|
|
|
|
}
|
|
|
|
hashL := hash8(cv, lTableBits)
|
|
|
|
hashS := hash4(cv, sTableBits)
|
|
|
|
candidateL := lTable[hashL]
|
|
|
|
candidateS := sTable[hashS]
|
|
|
|
|
|
|
|
score := func(m match) int {
|
|
|
|
// Matches that are longer forward are penalized since we must emit it as a literal.
|
|
|
|
score := m.length - m.s
|
|
|
|
if nextEmit == m.s {
|
|
|
|
// If we do not have to emit literals, we save 1 byte
|
|
|
|
score++
|
|
|
|
}
|
|
|
|
offset := m.s - m.offset
|
|
|
|
|
2022-09-28 18:30:40 +01:00
|
|
|
return score - emitCopyNoRepeatSize(offset, m.length)
|
2022-07-03 11:08:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
matchAt := func(offset, s int, first uint32) match {
|
|
|
|
if best.length != 0 && best.s-best.offset == s-offset {
|
|
|
|
// Don't retest if we have the same offset.
|
|
|
|
return match{offset: offset, s: s}
|
|
|
|
}
|
|
|
|
if load32(src, offset) != first {
|
|
|
|
return match{offset: offset, s: s}
|
|
|
|
}
|
|
|
|
m := match{offset: offset, s: s, length: 4 + offset}
|
|
|
|
s += 4
|
|
|
|
for s <= sLimit {
|
|
|
|
if diff := load64(src, s) ^ load64(src, m.length); diff != 0 {
|
|
|
|
m.length += bits.TrailingZeros64(diff) >> 3
|
|
|
|
break
|
|
|
|
}
|
|
|
|
s += 8
|
|
|
|
m.length += 8
|
|
|
|
}
|
|
|
|
m.length -= offset
|
|
|
|
m.score = score(m)
|
|
|
|
if m.score <= -m.s {
|
|
|
|
// Eliminate if no savings, we might find a better one.
|
|
|
|
m.length = 0
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
bestOf := func(a, b match) match {
|
|
|
|
if b.length == 0 {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
if a.length == 0 {
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
as := a.score + b.s
|
|
|
|
bs := b.score + a.s
|
|
|
|
if as >= bs {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
best = bestOf(matchAt(getCur(candidateL), s, uint32(cv)), matchAt(getPrev(candidateL), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getCur(candidateS), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getPrev(candidateS), s, uint32(cv)))
|
|
|
|
|
|
|
|
{
|
|
|
|
best = bestOf(best, matchAt(s-repeat+1, s+1, uint32(cv>>8)))
|
|
|
|
if best.length > 0 {
|
|
|
|
// s+1
|
|
|
|
nextShort := sTable[hash4(cv>>8, sTableBits)]
|
|
|
|
s := s + 1
|
|
|
|
cv := load64(src, s)
|
|
|
|
nextLong := lTable[hash8(cv, lTableBits)]
|
|
|
|
best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv)))
|
|
|
|
// Repeat at + 2
|
|
|
|
best = bestOf(best, matchAt(s-repeat+1, s+1, uint32(cv>>8)))
|
|
|
|
|
|
|
|
// s+2
|
|
|
|
if true {
|
|
|
|
nextShort = sTable[hash4(cv>>8, sTableBits)]
|
|
|
|
s++
|
|
|
|
cv = load64(src, s)
|
|
|
|
nextLong = lTable[hash8(cv, lTableBits)]
|
|
|
|
best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv)))
|
|
|
|
best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv)))
|
|
|
|
}
|
|
|
|
// Search for a match at best match end, see if that is better.
|
|
|
|
if sAt := best.s + best.length; sAt < sLimit {
|
|
|
|
sBack := best.s
|
|
|
|
backL := best.length
|
|
|
|
// Load initial values
|
|
|
|
cv = load64(src, sBack)
|
|
|
|
// Search for mismatch
|
|
|
|
next := lTable[hash8(load64(src, sAt), lTableBits)]
|
|
|
|
//next := sTable[hash4(load64(src, sAt), sTableBits)]
|
|
|
|
|
|
|
|
if checkAt := getCur(next) - backL; checkAt > 0 {
|
|
|
|
best = bestOf(best, matchAt(checkAt, sBack, uint32(cv)))
|
|
|
|
}
|
|
|
|
if checkAt := getPrev(next) - backL; checkAt > 0 {
|
|
|
|
best = bestOf(best, matchAt(checkAt, sBack, uint32(cv)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update table
|
|
|
|
lTable[hashL] = uint64(s) | candidateL<<32
|
|
|
|
sTable[hashS] = uint64(s) | candidateS<<32
|
|
|
|
|
|
|
|
if best.length > 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
cv = load64(src, nextS)
|
|
|
|
s = nextS
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extend backwards, not needed for repeats...
|
|
|
|
s = best.s
|
|
|
|
if true {
|
|
|
|
for best.offset > 0 && s > nextEmit && src[best.offset-1] == src[s-1] {
|
|
|
|
best.offset--
|
|
|
|
best.length++
|
|
|
|
s--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if false && best.offset >= s {
|
|
|
|
panic(fmt.Errorf("t %d >= s %d", best.offset, s))
|
|
|
|
}
|
|
|
|
// Bail if we exceed the maximum size.
|
|
|
|
if d+(s-nextEmit) > dstLimit {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
base := s
|
|
|
|
offset := s - best.offset
|
|
|
|
|
|
|
|
s += best.length
|
|
|
|
|
|
|
|
if offset > 65535 && s-base <= 5 {
|
|
|
|
// Bail if the match is equal or worse to the encoding.
|
|
|
|
s = best.s + 1
|
|
|
|
if s >= sLimit {
|
|
|
|
goto emitRemainder
|
|
|
|
}
|
|
|
|
cv = load64(src, s)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
d += emitLiteral(dst[d:], src[nextEmit:base])
|
|
|
|
d += emitCopyNoRepeat(dst[d:], offset, best.length)
|
|
|
|
repeat = offset
|
|
|
|
|
|
|
|
nextEmit = s
|
|
|
|
if s >= sLimit {
|
|
|
|
goto emitRemainder
|
|
|
|
}
|
|
|
|
|
|
|
|
if d > dstLimit {
|
|
|
|
// Do we have space for more, if not bail.
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
// Fill tables...
|
|
|
|
for i := best.s + 1; i < s; i++ {
|
|
|
|
cv0 := load64(src, i)
|
|
|
|
long0 := hash8(cv0, lTableBits)
|
|
|
|
short0 := hash4(cv0, sTableBits)
|
|
|
|
lTable[long0] = uint64(i) | lTable[long0]<<32
|
|
|
|
sTable[short0] = uint64(i) | sTable[short0]<<32
|
|
|
|
}
|
|
|
|
cv = load64(src, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
emitRemainder:
|
|
|
|
if nextEmit < len(src) {
|
|
|
|
// Bail if we exceed the maximum size.
|
|
|
|
if d+len(src)-nextEmit > dstLimit {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
d += emitLiteral(dst[d:], src[nextEmit:])
|
|
|
|
}
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
// emitCopySize returns the size to encode the offset+length
|
|
|
|
//
|
|
|
|
// It assumes that:
|
2023-02-27 09:21:58 +00:00
|
|
|
//
|
2022-07-03 11:08:30 +01:00
|
|
|
// 1 <= offset && offset <= math.MaxUint32
|
|
|
|
// 4 <= length && length <= 1 << 24
|
|
|
|
func emitCopySize(offset, length int) int {
|
|
|
|
if offset >= 65536 {
|
|
|
|
i := 0
|
|
|
|
if length > 64 {
|
|
|
|
length -= 64
|
|
|
|
if length >= 4 {
|
|
|
|
// Emit remaining as repeats
|
|
|
|
return 5 + emitRepeatSize(offset, length)
|
|
|
|
}
|
|
|
|
i = 5
|
|
|
|
}
|
|
|
|
if length == 0 {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
return i + 5
|
|
|
|
}
|
|
|
|
|
|
|
|
// Offset no more than 2 bytes.
|
|
|
|
if length > 64 {
|
2022-09-28 18:30:40 +01:00
|
|
|
if offset < 2048 {
|
|
|
|
// Emit 8 bytes, then rest as repeats...
|
|
|
|
return 2 + emitRepeatSize(offset, length-8)
|
|
|
|
}
|
2022-07-03 11:08:30 +01:00
|
|
|
// Emit remaining as repeats, at least 4 bytes remain.
|
|
|
|
return 3 + emitRepeatSize(offset, length-60)
|
|
|
|
}
|
|
|
|
if length >= 12 || offset >= 2048 {
|
|
|
|
return 3
|
|
|
|
}
|
|
|
|
// Emit the remaining copy, encoded as 2 bytes.
|
|
|
|
return 2
|
|
|
|
}
|
|
|
|
|
2022-09-28 18:30:40 +01:00
|
|
|
// emitCopyNoRepeatSize returns the size to encode the offset+length
|
|
|
|
//
|
|
|
|
// It assumes that:
|
2023-02-27 09:21:58 +00:00
|
|
|
//
|
2022-09-28 18:30:40 +01:00
|
|
|
// 1 <= offset && offset <= math.MaxUint32
|
|
|
|
// 4 <= length && length <= 1 << 24
|
|
|
|
func emitCopyNoRepeatSize(offset, length int) int {
|
|
|
|
if offset >= 65536 {
|
|
|
|
return 5 + 5*(length/64)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Offset no more than 2 bytes.
|
|
|
|
if length > 64 {
|
|
|
|
// Emit remaining as repeats, at least 4 bytes remain.
|
|
|
|
return 3 + 3*(length/60)
|
|
|
|
}
|
|
|
|
if length >= 12 || offset >= 2048 {
|
|
|
|
return 3
|
|
|
|
}
|
|
|
|
// Emit the remaining copy, encoded as 2 bytes.
|
|
|
|
return 2
|
|
|
|
}
|
|
|
|
|
2022-07-03 11:08:30 +01:00
|
|
|
// emitRepeatSize returns the number of bytes required to encode a repeat.
|
|
|
|
// Length must be at least 4 and < 1<<24
|
|
|
|
func emitRepeatSize(offset, length int) int {
|
|
|
|
// Repeat offset, make length cheaper
|
|
|
|
if length <= 4+4 || (length < 8+4 && offset < 2048) {
|
|
|
|
return 2
|
|
|
|
}
|
|
|
|
if length < (1<<8)+4+4 {
|
|
|
|
return 3
|
|
|
|
}
|
|
|
|
if length < (1<<16)+(1<<8)+4 {
|
|
|
|
return 4
|
|
|
|
}
|
|
|
|
const maxRepeat = (1 << 24) - 1
|
|
|
|
length -= (1 << 16) - 4
|
|
|
|
left := 0
|
|
|
|
if length > maxRepeat {
|
|
|
|
left = length - maxRepeat + 4
|
|
|
|
}
|
|
|
|
if left > 0 {
|
|
|
|
return 5 + emitRepeatSize(offset, left)
|
|
|
|
}
|
|
|
|
return 5
|
|
|
|
}
|