Skip to content

Commit 2f99358

Browse files
klauspostgreatroar
andauthored
zstd: Improve zstd best efficiency (#784)
* zstd: Improve best encoder by extending backwards Before/after compared to master: ``` enwik8 zskp 4 100000000 29798035 5596 17.04 enwik8 zskp 4 100000000 29298617 5678 16.79 silesia.tar zskp 4 211947520 59904436 10622 19.03 silesia.tar zskp 4 211947520 59311059 10818 18.68 TS40.txt zskp 4 400000000 123504912 22005 17.34 TS40.txt zskp 4 400000000 121367006 23661 16.12 apache.log zskp 4 2622574440 113537595 21239 117.76 apache.log zskp 4 2622574440 110106407 24301 102.92 github-ranks-backup.bin zskp 4 1862623243 377801114 75884 23.41 github-ranks-backup.bin zskp 4 1862623243 373322075 76540 23.21 ``` Doubles table size. --------- Co-authored-by: greatroar <[email protected]>
1 parent 69a8ecc commit 2f99358

File tree

1 file changed

+70
-99
lines changed

1 file changed

+70
-99
lines changed

zstd/enc_best.go

Lines changed: 70 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
const (
15-
bestLongTableBits = 22 // Bits used in the long match table
15+
bestLongTableBits = 23 // Bits used in the long match table
1616
bestLongTableSize = 1 << bestLongTableBits // Size of the table
1717
bestLongLen = 8 // Bytes used for table hash
1818

@@ -188,7 +188,7 @@ encodeLoop:
188188
panic("offset0 was 0")
189189
}
190190

191-
const goodEnough = 100
191+
const goodEnough = 250
192192

193193
nextHashL := hashLen(cv, bestLongTableBits, bestLongLen)
194194
nextHashS := hashLen(cv, bestShortTableBits, bestShortLen)
@@ -205,7 +205,37 @@ encodeLoop:
205205
panic(fmt.Sprintf("first match mismatch: %v != %v, first: %08x", src[s:s+4], src[offset:offset+4], first))
206206
}
207207
}
208-
cand := match{offset: offset, s: s, length: 4 + e.matchlen(s+4, offset+4, src), rep: rep}
208+
// Try to quick reject if we already have a long match.
209+
if m.length > 16 {
210+
left := len(src) - int(m.s+m.length)
211+
// If we are too close to the end, keep as is.
212+
if left <= 0 {
213+
return
214+
}
215+
if left > 2 {
216+
// Check 4 bytes, 4 bytes from the end of the current match.
217+
a := load3232(src, offset+m.length-8)
218+
b := load3232(src, s+m.length-8)
219+
if a != b {
220+
return
221+
}
222+
}
223+
}
224+
l := 4 + e.matchlen(s+4, offset+4, src)
225+
if rep < 0 {
226+
// Extend candidate match backwards as far as possible.
227+
tMin := s - e.maxMatchOff
228+
if tMin < 0 {
229+
tMin = 0
230+
}
231+
for offset > tMin && s > nextEmit && src[offset-1] == src[s-1] && l < maxMatchLength {
232+
s--
233+
offset--
234+
l++
235+
}
236+
}
237+
238+
cand := match{offset: offset, s: s, length: l, rep: rep}
209239
cand.estBits(bitsPerByte)
210240
if m.est >= highScore || cand.est-m.est+(cand.s-m.s)*bitsPerByte>>10 < 0 {
211241
*m = cand
@@ -219,17 +249,29 @@ encodeLoop:
219249
improve(&best, candidateS.prev-e.cur, s, uint32(cv), -1)
220250

221251
if canRepeat && best.length < goodEnough {
222-
cv32 := uint32(cv >> 8)
223-
spp := s + 1
224-
improve(&best, spp-offset1, spp, cv32, 1)
225-
improve(&best, spp-offset2, spp, cv32, 2)
226-
improve(&best, spp-offset3, spp, cv32, 3)
227-
if best.length > 0 {
228-
cv32 = uint32(cv >> 24)
229-
spp += 2
252+
if s == nextEmit {
253+
// Check repeats straight after a match.
254+
improve(&best, s-offset2, s, uint32(cv), 1|4)
255+
improve(&best, s-offset3, s, uint32(cv), 2|4)
256+
if offset1 > 1 {
257+
improve(&best, s-(offset1-1), s, uint32(cv), 3|4)
258+
}
259+
}
260+
261+
// If either no match or a non-repeat match, check at + 1
262+
if best.rep <= 0 {
263+
cv32 := uint32(cv >> 8)
264+
spp := s + 1
230265
improve(&best, spp-offset1, spp, cv32, 1)
231266
improve(&best, spp-offset2, spp, cv32, 2)
232267
improve(&best, spp-offset3, spp, cv32, 3)
268+
if best.rep < 0 {
269+
cv32 = uint32(cv >> 24)
270+
spp += 2
271+
improve(&best, spp-offset1, spp, cv32, 1)
272+
improve(&best, spp-offset2, spp, cv32, 2)
273+
improve(&best, spp-offset3, spp, cv32, 3)
274+
}
233275
}
234276
}
235277
// Load next and check...
@@ -248,7 +290,7 @@ encodeLoop:
248290
continue
249291
}
250292

251-
s++
293+
s := s + 1
252294
candidateS = e.table[hashLen(cv>>8, bestShortTableBits, bestShortLen)]
253295
cv = load6432(src, s)
254296
cv2 := load6432(src, s+1)
@@ -292,38 +334,22 @@ encodeLoop:
292334

293335
// We have a match, we can store the forward value
294336
if best.rep > 0 {
295-
s = best.s
296337
var seq seq
297338
seq.matchLen = uint32(best.length - zstdMinMatch)
298-
299-
// We might be able to match backwards.
300-
// Extend as long as we can.
301-
start := best.s
302-
// We end the search early, so we don't risk 0 literals
303-
// and have to do special offset treatment.
304-
startLimit := nextEmit + 1
305-
306-
tMin := s - e.maxMatchOff
307-
if tMin < 0 {
308-
tMin = 0
309-
}
310-
repIndex := best.offset
311-
for repIndex > tMin && start > startLimit && src[repIndex-1] == src[start-1] && seq.matchLen < maxMatchLength-zstdMinMatch-1 {
312-
repIndex--
313-
start--
314-
seq.matchLen++
339+
if debugAsserts && s <= nextEmit {
340+
panic("s <= nextEmit")
315341
}
316-
addLiterals(&seq, start)
342+
addLiterals(&seq, best.s)
317343

318-
// rep 0
319-
seq.offset = uint32(best.rep)
344+
// Repeat. If bit 4 is set, this is a non-lit repeat.
345+
seq.offset = uint32(best.rep & 3)
320346
if debugSequences {
321347
println("repeat sequence", seq, "next s:", s)
322348
}
323349
blk.sequences = append(blk.sequences, seq)
324350

325-
// Index match start+1 (long) -> s - 1
326-
index0 := s
351+
// Index old s + 1 -> s - 1
352+
index0 := s + 1
327353
s = best.s + best.length
328354

329355
nextEmit = s
@@ -336,7 +362,7 @@ encodeLoop:
336362
}
337363
// Index skipped...
338364
off := index0 + e.cur
339-
for index0 < s-1 {
365+
for index0 < s {
340366
cv0 := load6432(src, index0)
341367
h0 := hashLen(cv0, bestLongTableBits, bestLongLen)
342368
h1 := hashLen(cv0, bestShortTableBits, bestShortLen)
@@ -346,17 +372,20 @@ encodeLoop:
346372
index0++
347373
}
348374
switch best.rep {
349-
case 2:
375+
case 2, 4 | 1:
350376
offset1, offset2 = offset2, offset1
351-
case 3:
377+
case 3, 4 | 2:
352378
offset1, offset2, offset3 = offset3, offset1, offset2
379+
case 4 | 3:
380+
offset1, offset2, offset3 = offset1-1, offset1, offset2
353381
}
354382
cv = load6432(src, s)
355383
continue
356384
}
357385

358386
// A 4-byte match has been found. Update recent offsets.
359387
// We'll later see if more than 4 bytes.
388+
index0 := s + 1
360389
s = best.s
361390
t := best.offset
362391
offset1, offset2, offset3 = s-t, offset1, offset2
@@ -369,22 +398,9 @@ encodeLoop:
369398
panic("invalid offset")
370399
}
371400

372-
// Extend the n-byte match as long as possible.
373-
l := best.length
374-
375-
// Extend backwards
376-
tMin := s - e.maxMatchOff
377-
if tMin < 0 {
378-
tMin = 0
379-
}
380-
for t > tMin && s > nextEmit && src[t-1] == src[s-1] && l < maxMatchLength {
381-
s--
382-
t--
383-
l++
384-
}
385-
386401
// Write our sequence
387402
var seq seq
403+
l := best.length
388404
seq.litLen = uint32(s - nextEmit)
389405
seq.matchLen = uint32(l - zstdMinMatch)
390406
if seq.litLen > 0 {
@@ -401,10 +417,8 @@ encodeLoop:
401417
break encodeLoop
402418
}
403419

404-
// Index match start+1 (long) -> s - 1
405-
index0 := s - l + 1
406-
// every entry
407-
for index0 < s-1 {
420+
// Index old s + 1 -> s - 1
421+
for index0 < s {
408422
cv0 := load6432(src, index0)
409423
h0 := hashLen(cv0, bestLongTableBits, bestLongLen)
410424
h1 := hashLen(cv0, bestShortTableBits, bestShortLen)
@@ -413,50 +427,7 @@ encodeLoop:
413427
e.table[h1] = prevEntry{offset: off, prev: e.table[h1].offset}
414428
index0++
415429
}
416-
417430
cv = load6432(src, s)
418-
if !canRepeat {
419-
continue
420-
}
421-
422-
// Check offset 2
423-
for {
424-
o2 := s - offset2
425-
if load3232(src, o2) != uint32(cv) {
426-
// Do regular search
427-
break
428-
}
429-
430-
// Store this, since we have it.
431-
nextHashS := hashLen(cv, bestShortTableBits, bestShortLen)
432-
nextHashL := hashLen(cv, bestLongTableBits, bestLongLen)
433-
434-
// We have at least 4 byte match.
435-
// No need to check backwards. We come straight from a match
436-
l := 4 + e.matchlen(s+4, o2+4, src)
437-
438-
e.longTable[nextHashL] = prevEntry{offset: s + e.cur, prev: e.longTable[nextHashL].offset}
439-
e.table[nextHashS] = prevEntry{offset: s + e.cur, prev: e.table[nextHashS].offset}
440-
seq.matchLen = uint32(l) - zstdMinMatch
441-
seq.litLen = 0
442-
443-
// Since litlen is always 0, this is offset 1.
444-
seq.offset = 1
445-
s += l
446-
nextEmit = s
447-
if debugSequences {
448-
println("sequence", seq, "next s:", s)
449-
}
450-
blk.sequences = append(blk.sequences, seq)
451-
452-
// Swap offset 1 and 2.
453-
offset1, offset2 = offset2, offset1
454-
if s >= sLimit {
455-
// Finished
456-
break encodeLoop
457-
}
458-
cv = load6432(src, s)
459-
}
460431
}
461432

462433
if int(nextEmit) < len(src) {

0 commit comments

Comments
 (0)