From dfaad36092f8938cb1ba19cc583cbd2e00c3c86b Mon Sep 17 00:00:00 2001 From: Klaus Post Date: Wed, 30 Nov 2022 18:20:06 +0100 Subject: [PATCH] zstd: Use individual reset threshold (#703) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * zstd: Use individual reset threshold Instead of setting the limit to the largest window size, set it to the chosen one. ``` λ benchcmp before.txt after.txt benchmark old ns/op new ns/op delta BenchmarkEncoder_EncodeAllSimple4K/fastest-32 3145 3133 -0.38% BenchmarkEncoder_EncodeAllSimple4K/default-32 41485 40624 -2.08% BenchmarkEncoder_EncodeAllSimple4K/better-32 49352 49197 -0.31% BenchmarkEncoder_EncodeAllSimple4K/best-32 421522 407392 -3.35% benchmark old MB/s new MB/s speedup BenchmarkEncoder_EncodeAllSimple4K/fastest-32 1302.48 1307.39 1.00x BenchmarkEncoder_EncodeAllSimple4K/default-32 98.74 100.83 1.02x BenchmarkEncoder_EncodeAllSimple4K/better-32 83.00 83.26 1.00x BenchmarkEncoder_EncodeAllSimple4K/best-32 9.72 10.05 1.03x ``` --- zstd/enc_base.go | 7 ++++--- zstd/enc_best.go | 2 +- zstd/enc_better.go | 4 ++-- zstd/enc_dfast.go | 8 ++++---- zstd/enc_fast.go | 8 ++++---- zstd/encoder_options.go | 15 ++++++++------- zstd/fuzz_test.go | 4 ++-- zstd/zstd.go | 3 --- 8 files changed, 25 insertions(+), 26 deletions(-) diff --git a/zstd/enc_base.go b/zstd/enc_base.go index 2760308d0b..bfb2e146c3 100644 --- a/zstd/enc_base.go +++ b/zstd/enc_base.go @@ -16,6 +16,7 @@ type fastBase struct { cur int32 // maximum offset. Should be at least 2x block size. maxMatchOff int32 + bufferReset int32 hist []byte crc *xxhash.Digest tmp [8]byte @@ -56,8 +57,8 @@ func (e *fastBase) Block() *blockEnc { } func (e *fastBase) addBlock(src []byte) int32 { - if debugAsserts && e.cur > bufferReset { - panic(fmt.Sprintf("ecur (%d) > buffer reset (%d)", e.cur, bufferReset)) + if debugAsserts && e.cur > e.bufferReset { + panic(fmt.Sprintf("ecur (%d) > buffer reset (%d)", e.cur, e.bufferReset)) } // check if we have space already if len(e.hist)+len(src) > cap(e.hist) { @@ -154,7 +155,7 @@ func (e *fastBase) resetBase(d *dict, singleBlock bool) { // We offset current position so everything will be out of reach. // If above reset line, history will be purged. - if e.cur < bufferReset { + if e.cur < e.bufferReset { e.cur += e.maxMatchOff + int32(len(e.hist)) } e.hist = e.hist[:0] diff --git a/zstd/enc_best.go b/zstd/enc_best.go index 0d1a60388f..35675e4df0 100644 --- a/zstd/enc_best.go +++ b/zstd/enc_best.go @@ -85,7 +85,7 @@ func (e *bestFastEncoder) Encode(blk *blockEnc, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= bufferReset { + for e.cur >= e.bufferReset-int32(len(e.hist)) { if len(e.hist) == 0 { e.table = [bestShortTableSize]prevEntry{} e.longTable = [bestLongTableSize]prevEntry{} diff --git a/zstd/enc_better.go b/zstd/enc_better.go index 0a8518b3cb..8582f31a7c 100644 --- a/zstd/enc_better.go +++ b/zstd/enc_better.go @@ -62,7 +62,7 @@ func (e *betterFastEncoder) Encode(blk *blockEnc, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= bufferReset { + for e.cur >= e.bufferReset-int32(len(e.hist)) { if len(e.hist) == 0 { e.table = [betterShortTableSize]tableEntry{} e.longTable = [betterLongTableSize]prevEntry{} @@ -583,7 +583,7 @@ func (e *betterFastEncoderDict) Encode(blk *blockEnc, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= bufferReset { + for e.cur >= e.bufferReset-int32(len(e.hist)) { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} diff --git a/zstd/enc_dfast.go b/zstd/enc_dfast.go index f25db686e4..7d425109ad 100644 --- a/zstd/enc_dfast.go +++ b/zstd/enc_dfast.go @@ -44,7 +44,7 @@ func (e *doubleFastEncoder) Encode(blk *blockEnc, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= bufferReset { + for e.cur >= e.bufferReset-int32(len(e.hist)) { if len(e.hist) == 0 { e.table = [dFastShortTableSize]tableEntry{} e.longTable = [dFastLongTableSize]tableEntry{} @@ -384,7 +384,7 @@ func (e *doubleFastEncoder) EncodeNoHist(blk *blockEnc, src []byte) { ) // Protect against e.cur wraparound. - if e.cur >= bufferReset { + if e.cur >= e.bufferReset { for i := range e.table[:] { e.table[i] = tableEntry{} } @@ -681,7 +681,7 @@ encodeLoop: } // We do not store history, so we must offset e.cur to avoid false matches for next user. - if e.cur < bufferReset { + if e.cur < e.bufferReset { e.cur += int32(len(src)) } } @@ -696,7 +696,7 @@ func (e *doubleFastEncoderDict) Encode(blk *blockEnc, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= bufferReset { + for e.cur >= e.bufferReset-int32(len(e.hist)) { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} diff --git a/zstd/enc_fast.go b/zstd/enc_fast.go index b8aaa00a88..315b1a8f2f 100644 --- a/zstd/enc_fast.go +++ b/zstd/enc_fast.go @@ -43,7 +43,7 @@ func (e *fastEncoder) Encode(blk *blockEnc, src []byte) { ) // Protect against e.cur wraparound. - for e.cur >= bufferReset { + for e.cur >= e.bufferReset-int32(len(e.hist)) { if len(e.hist) == 0 { for i := range e.table[:] { e.table[i] = tableEntry{} @@ -310,7 +310,7 @@ func (e *fastEncoder) EncodeNoHist(blk *blockEnc, src []byte) { } // Protect against e.cur wraparound. - if e.cur >= bufferReset { + if e.cur >= e.bufferReset { for i := range e.table[:] { e.table[i] = tableEntry{} } @@ -538,7 +538,7 @@ encodeLoop: println("returning, recent offsets:", blk.recentOffsets, "extra literals:", blk.extraLits) } // We do not store history, so we must offset e.cur to avoid false matches for next user. - if e.cur < bufferReset { + if e.cur < e.bufferReset { e.cur += int32(len(src)) } } @@ -555,7 +555,7 @@ func (e *fastEncoderDict) Encode(blk *blockEnc, src []byte) { return } // Protect against e.cur wraparound. - for e.cur >= bufferReset { + for e.cur >= e.bufferReset-int32(len(e.hist)) { if len(e.hist) == 0 { e.table = [tableSize]tableEntry{} e.cur = e.maxMatchOff diff --git a/zstd/encoder_options.go b/zstd/encoder_options.go index a7c5e1aac4..6015f498af 100644 --- a/zstd/encoder_options.go +++ b/zstd/encoder_options.go @@ -3,6 +3,7 @@ package zstd import ( "errors" "fmt" + "math" "runtime" "strings" ) @@ -47,22 +48,22 @@ func (o encoderOptions) encoder() encoder { switch o.level { case SpeedFastest: if o.dict != nil { - return &fastEncoderDict{fastEncoder: fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), lowMem: o.lowMem}}} + return &fastEncoderDict{fastEncoder: fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), bufferReset: math.MaxInt32 - int32(o.windowSize*2), lowMem: o.lowMem}}} } - return &fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), lowMem: o.lowMem}} + return &fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), bufferReset: math.MaxInt32 - int32(o.windowSize*2), lowMem: o.lowMem}} case SpeedDefault: if o.dict != nil { - return &doubleFastEncoderDict{fastEncoderDict: fastEncoderDict{fastEncoder: fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), lowMem: o.lowMem}}}} + return &doubleFastEncoderDict{fastEncoderDict: fastEncoderDict{fastEncoder: fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), bufferReset: math.MaxInt32 - int32(o.windowSize*2), lowMem: o.lowMem}}}} } - return &doubleFastEncoder{fastEncoder: fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), lowMem: o.lowMem}}} + return &doubleFastEncoder{fastEncoder: fastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), bufferReset: math.MaxInt32 - int32(o.windowSize*2), lowMem: o.lowMem}}} case SpeedBetterCompression: if o.dict != nil { - return &betterFastEncoderDict{betterFastEncoder: betterFastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), lowMem: o.lowMem}}} + return &betterFastEncoderDict{betterFastEncoder: betterFastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), bufferReset: math.MaxInt32 - int32(o.windowSize*2), lowMem: o.lowMem}}} } - return &betterFastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), lowMem: o.lowMem}} + return &betterFastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), bufferReset: math.MaxInt32 - int32(o.windowSize*2), lowMem: o.lowMem}} case SpeedBestCompression: - return &bestFastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), lowMem: o.lowMem}} + return &bestFastEncoder{fastBase: fastBase{maxMatchOff: int32(o.windowSize), bufferReset: math.MaxInt32 - int32(o.windowSize*2), lowMem: o.lowMem}} } panic("unknown compression level") } diff --git a/zstd/fuzz_test.go b/zstd/fuzz_test.go index 8ac26bcdcd..3a79d808bc 100644 --- a/zstd/fuzz_test.go +++ b/zstd/fuzz_test.go @@ -181,8 +181,8 @@ func FuzzEncoding(f *testing.F) { // Just test if we crash... defer func() { if r := recover(); r != nil { - rdebug.PrintStack() - t.Fatal(r) + stack := rdebug.Stack() + t.Fatalf("%v:\n%v", r, string(stack)) } }() if len(data) > maxSize { diff --git a/zstd/zstd.go b/zstd/zstd.go index 34b3cfdb08..b1886f7c74 100644 --- a/zstd/zstd.go +++ b/zstd/zstd.go @@ -36,9 +36,6 @@ const forcePreDef = false // zstdMinMatch is the minimum zstd match length. const zstdMinMatch = 3 -// Reset the buffer offset when reaching this. -const bufferReset = math.MaxInt32 - MaxWindowSize - // fcsUnknown is used for unknown frame content size. const fcsUnknown = math.MaxUint64