Skip to content

Commit 4fd183f

Browse files
authored
deflate: Improve entropy compression (#338)
Improve entropy coding and make various cleanups. Before: ``` file out level insize outsize millis mb/s dickens flatekp -2 1019244600 589553800 3460 280.86 dickens flatekp 1 1019244600 460143946 7778 124.96 dickens flatekp 2 1019244600 447624657 7993 121.60 dickens flatekp 3 1019244600 442275633 10105 96.19 dickens flatekp 4 1019244600 411669371 10310 94.28 dickens flatekp 5 1019244600 406856462 11506 84.48 dickens flatekp 6 1019244600 403864317 11894 81.72 dickens flatekp 7 1019244600 391734230 27703 35.09 dickens flatekp 8 1019244600 386774915 38643 25.15 dickens flatekp 9 1019244600 385598868 48084 20.21 ``` After: ``` file out level insize outsize millis mb/s dickens flatekp -2 1019244600 582799774 3812 254.97 dickens flatekp 1 1019244600 458664090 7490 129.76 dickens flatekp 2 1019244600 445420813 7872 123.47 dickens flatekp 3 1019244600 439874073 9659 100.63 dickens flatekp 4 1019244600 407860161 9766 99.52 dickens flatekp 5 1019244600 404161695 11432 85.02 dickens flatekp 6 1019244600 400997375 11605 83.75 dickens flatekp 7 1019244600 391734230 26570 36.58 dickens flatekp 8 1019244600 386774915 37690 25.79 dickens flatekp 9 1019244600 385598868 47283 20.56 ```
1 parent 5e8a147 commit 4fd183f

File tree

6 files changed

+62
-60
lines changed

6 files changed

+62
-60
lines changed

flate/deflate.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -645,15 +645,15 @@ func (d *compressor) init(w io.Writer, level int) (err error) {
645645
d.fill = (*compressor).fillBlock
646646
d.step = (*compressor).store
647647
case level == ConstantCompression:
648-
d.w.logNewTablePenalty = 4
649-
d.window = make([]byte, maxStoreBlockSize)
648+
d.w.logNewTablePenalty = 8
649+
d.window = make([]byte, 32<<10)
650650
d.fill = (*compressor).fillBlock
651651
d.step = (*compressor).storeHuff
652652
case level == DefaultCompression:
653653
level = 5
654654
fallthrough
655655
case level >= 1 && level <= 6:
656-
d.w.logNewTablePenalty = 6
656+
d.w.logNewTablePenalty = 8
657657
d.fast = newFastEnc(level)
658658
d.window = make([]byte, maxStoreBlockSize)
659659
d.fill = (*compressor).fillBlock

flate/fast_encoder.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package flate
77

88
import (
9+
"encoding/binary"
910
"fmt"
1011
"math/bits"
1112
)
@@ -65,26 +66,15 @@ func load32(b []byte, i int) uint32 {
6566
}
6667

6768
func load64(b []byte, i int) uint64 {
68-
// Help the compiler eliminate bounds checks on the read so it can be done in a single read.
69-
b = b[i:]
70-
b = b[:8]
71-
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
72-
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
69+
return binary.LittleEndian.Uint64(b[i:])
7370
}
7471

7572
func load3232(b []byte, i int32) uint32 {
76-
// Help the compiler eliminate bounds checks on the read so it can be done in a single read.
77-
b = b[i:]
78-
b = b[:4]
79-
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
73+
return binary.LittleEndian.Uint32(b[i:])
8074
}
8175

8276
func load6432(b []byte, i int32) uint64 {
83-
// Help the compiler eliminate bounds checks on the read so it can be done in a single read.
84-
b = b[i:]
85-
b = b[:8]
86-
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
87-
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
77+
return binary.LittleEndian.Uint64(b[i:])
8878
}
8979

9080
func hash(u uint32) uint32 {
@@ -225,9 +215,9 @@ func (e *fastGen) Reset() {
225215
func matchLen(a, b []byte) int {
226216
b = b[:len(a)]
227217
var checked int
228-
if len(a) > 4 {
218+
if len(a) >= 4 {
229219
// Try 4 bytes first
230-
if diff := load32(a, 0) ^ load32(b, 0); diff != 0 {
220+
if diff := binary.LittleEndian.Uint32(a) ^ binary.LittleEndian.Uint32(b); diff != 0 {
231221
return bits.TrailingZeros32(diff) >> 3
232222
}
233223
// Switch to 8 byte matching.
@@ -236,7 +226,7 @@ func matchLen(a, b []byte) int {
236226
b = b[4:]
237227
for len(a) >= 8 {
238228
b = b[:len(a)]
239-
if diff := load64(a, 0) ^ load64(b, 0); diff != 0 {
229+
if diff := binary.LittleEndian.Uint64(a) ^ binary.LittleEndian.Uint64(b); diff != 0 {
240230
return checked + (bits.TrailingZeros64(diff) >> 3)
241231
}
242232
checked += 8

flate/huffman_bit_writer.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package flate
66

77
import (
8+
"encoding/binary"
89
"io"
910
)
1011

@@ -206,7 +207,7 @@ func (w *huffmanBitWriter) write(b []byte) {
206207
}
207208

208209
func (w *huffmanBitWriter) writeBits(b int32, nb uint16) {
209-
w.bits |= uint64(b) << (w.nbits & reg16SizeMask64)
210+
w.bits |= uint64(b) << w.nbits
210211
w.nbits += nb
211212
if w.nbits >= 48 {
212213
w.writeOutBits()
@@ -420,13 +421,11 @@ func (w *huffmanBitWriter) writeOutBits() {
420421
w.bits >>= 48
421422
w.nbits -= 48
422423
n := w.nbytes
423-
w.bytes[n] = byte(bits)
424-
w.bytes[n+1] = byte(bits >> 8)
425-
w.bytes[n+2] = byte(bits >> 16)
426-
w.bytes[n+3] = byte(bits >> 24)
427-
w.bytes[n+4] = byte(bits >> 32)
428-
w.bytes[n+5] = byte(bits >> 40)
424+
425+
// We over-write, but faster...
426+
binary.LittleEndian.PutUint64(w.bytes[n:], bits)
429427
n += 6
428+
430429
if n >= bufferFlushSize {
431430
if w.err != nil {
432431
n = 0
@@ -435,6 +434,7 @@ func (w *huffmanBitWriter) writeOutBits() {
435434
w.write(w.bytes[:n])
436435
n = 0
437436
}
437+
438438
w.nbytes = n
439439
}
440440

@@ -759,7 +759,7 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode)
759759
} else {
760760
// inlined
761761
c := lengths[lengthCode&31]
762-
w.bits |= uint64(c.code) << (w.nbits & reg16SizeMask64)
762+
w.bits |= uint64(c.code) << w.nbits
763763
w.nbits += c.len
764764
if w.nbits >= 48 {
765765
w.writeOutBits()
@@ -779,7 +779,7 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode)
779779
} else {
780780
// inlined
781781
c := offs[offsetCode&31]
782-
w.bits |= uint64(c.code) << (w.nbits & reg16SizeMask64)
782+
w.bits |= uint64(c.code) << w.nbits
783783
w.nbits += c.len
784784
if w.nbits >= 48 {
785785
w.writeOutBits()
@@ -830,8 +830,8 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte, sync bool) {
830830
// Assume header is around 70 bytes:
831831
// https://stackoverflow.com/a/25454430
832832
const guessHeaderSizeBits = 70 * 8
833-
estBits, estExtra := histogramSize(input, w.literalFreq[:], !eof && !sync)
834-
estBits += w.lastHeader + 15
833+
estBits := histogramSize(input, w.literalFreq[:], !eof && !sync)
834+
estBits += w.lastHeader + len(input)/32
835835
if w.lastHeader == 0 {
836836
estBits += guessHeaderSizeBits
837837
}
@@ -845,9 +845,9 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte, sync bool) {
845845
return
846846
}
847847

848+
reuseSize := 0
848849
if w.lastHeader > 0 {
849-
reuseSize := w.literalEncoding.bitLength(w.literalFreq[:256])
850-
estBits += estExtra
850+
reuseSize = w.literalEncoding.bitLength(w.literalFreq[:256])
851851

852852
if estBits < reuseSize {
853853
// We owe an EOB
@@ -859,6 +859,10 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte, sync bool) {
859859
const numLiterals = endBlockMarker + 1
860860
const numOffsets = 1
861861
if w.lastHeader == 0 {
862+
if !eof && !sync {
863+
// Generate a slightly suboptimal tree that can be used for all.
864+
fillHist(w.literalFreq[:numLiterals])
865+
}
862866
w.literalFreq[endBlockMarker] = 1
863867
w.literalEncoding.generate(w.literalFreq[:numLiterals], 15)
864868

@@ -878,19 +882,14 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte, sync bool) {
878882
for _, t := range input {
879883
// Bitwriting inlined, ~30% speedup
880884
c := encoding[t]
881-
w.bits |= uint64(c.code) << ((w.nbits) & reg16SizeMask64)
885+
w.bits |= uint64(c.code) << w.nbits
882886
w.nbits += c.len
883887
if w.nbits >= 48 {
884888
bits := w.bits
885889
w.bits >>= 48
886890
w.nbits -= 48
887891
n := w.nbytes
888-
w.bytes[n] = byte(bits)
889-
w.bytes[n+1] = byte(bits >> 8)
890-
w.bytes[n+2] = byte(bits >> 16)
891-
w.bytes[n+3] = byte(bits >> 24)
892-
w.bytes[n+4] = byte(bits >> 32)
893-
w.bytes[n+5] = byte(bits >> 40)
892+
binary.LittleEndian.PutUint64(w.bytes[n:], bits)
894893
n += 6
895894
if n >= bufferFlushSize {
896895
if w.err != nil {

flate/huffman_code.go

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ func (h *huffmanEncoder) bitLength(freq []uint16) int {
122122
return total
123123
}
124124

125+
func (h *huffmanEncoder) bitLengthRaw(b []byte) int {
126+
var total int
127+
for _, f := range b {
128+
if f != 0 {
129+
total += int(h.codes[f].len)
130+
}
131+
}
132+
return total
133+
}
134+
125135
// Return the number of literals assigned to each bit size in the Huffman encoding
126136
//
127137
// This method is only called when list.length >= 3
@@ -327,37 +337,40 @@ func atLeastOne(v float32) float32 {
327337
return v
328338
}
329339

340+
// Unassigned values are assigned '1' in the histogram.
341+
func fillHist(b []uint16) {
342+
for i, v := range b {
343+
if v == 0 {
344+
b[i] = 1
345+
}
346+
}
347+
}
348+
330349
// histogramSize accumulates a histogram of b in h.
331350
// An estimated size in bits is returned.
332-
// Unassigned values are assigned '1' in the histogram.
333351
// len(h) must be >= 256, and h's elements must be all zeroes.
334-
func histogramSize(b []byte, h []uint16, fill bool) (int, int) {
352+
func histogramSize(b []byte, h []uint16, fill bool) (bits int) {
335353
h = h[:256]
336354
for _, t := range b {
337355
h[t]++
338356
}
339-
invTotal := 1.0 / float32(len(b))
340-
shannon := float32(0.0)
341-
var extra float32
357+
total := len(b)
342358
if fill {
343-
oneBits := atLeastOne(-mFastLog2(invTotal))
344-
for i, v := range h[:] {
345-
if v > 0 {
346-
n := float32(v)
347-
shannon += atLeastOne(-mFastLog2(n*invTotal)) * n
348-
} else {
349-
h[i] = 1
350-
extra += oneBits
359+
for _, v := range h {
360+
if v == 0 {
361+
total++
351362
}
352363
}
353-
} else {
354-
for _, v := range h[:] {
355-
if v > 0 {
356-
n := float32(v)
357-
shannon += atLeastOne(-mFastLog2(n*invTotal)) * n
358-
}
364+
}
365+
366+
invTotal := 1.0 / float32(total)
367+
shannon := float32(0.0)
368+
for _, v := range h {
369+
if v > 0 {
370+
n := float32(v)
371+
shannon += atLeastOne(-mFastLog2(n*invTotal)) * n
359372
}
360373
}
361374

362-
return int(shannon + 0.99), int(extra + 0.99)
375+
return int(shannon + 0.99)
363376
}
13 Bytes
Binary file not shown.
6 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)