Skip to content

Commit b4b877f

Browse files
committed
compress/flate, archive/zip: reduce memory allocations
1 parent cedf500 commit b4b877f

File tree

3 files changed

+80
-7
lines changed

3 files changed

+80
-7
lines changed

src/archive/zip/zip_test.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@ func TestOver65kFiles(t *testing.T) {
2424
if testing.Short() && testenv.Builder() == "" {
2525
t.Skip("skipping in short mode")
2626
}
27+
28+
memStatBeforeWriter := runtime.MemStats{}
29+
runtime.GC() // to guarantee mem stat is up to date
30+
runtime.ReadMemStats(&memStatBeforeWriter)
31+
2732
buf := new(strings.Builder)
2833
w := NewWriter(buf)
2934
const nFiles = (1 << 16) + 42
3035
for i := 0; i < nFiles; i++ {
3136
_, err := w.CreateHeader(&FileHeader{
3237
Name: fmt.Sprintf("%d.dat", i),
33-
Method: Store, // avoid Issue 6136 and Issue 6138
38+
Method: Deflate,
3439
})
3540
if err != nil {
3641
t.Fatalf("creating file %d: %v", i, err)
@@ -39,6 +44,11 @@ func TestOver65kFiles(t *testing.T) {
3944
if err := w.Close(); err != nil {
4045
t.Fatalf("Writer.Close: %v", err)
4146
}
47+
48+
memStatBeforeReader := runtime.MemStats{}
49+
runtime.GC()
50+
runtime.ReadMemStats(&memStatBeforeReader)
51+
4252
s := buf.String()
4353
zr, err := NewReader(strings.NewReader(s), int64(len(s)))
4454
if err != nil {
@@ -53,6 +63,26 @@ func TestOver65kFiles(t *testing.T) {
5363
t.Fatalf("File(%d) = %q, want %q", i, zr.File[i].Name, want)
5464
}
5565
}
66+
for i := 0; i < nFiles; i++ {
67+
f, err := zr.File[i].Open()
68+
if err != nil {
69+
t.Fatalf("File(%d).Open: %v", i, err)
70+
}
71+
if err := f.Close(); err != nil {
72+
t.Fatalf("File(%d).Open().Close: %v", i, err)
73+
}
74+
}
75+
76+
memStatAfter := runtime.MemStats{}
77+
runtime.GC()
78+
runtime.ReadMemStats(&memStatAfter)
79+
80+
if alloc := memStatBeforeReader.TotalAlloc - memStatBeforeWriter.TotalAlloc; alloc > 150<<20 {
81+
t.Errorf("allocated too much (%v MB) creating zip file", alloc>>20)
82+
}
83+
if alloc := memStatAfter.TotalAlloc - memStatBeforeReader.TotalAlloc; alloc > 70<<20 {
84+
t.Errorf("allocated too much (%v MB) reading zip file", alloc>>20)
85+
}
5686
}
5787

5888
func TestModTime(t *testing.T) {

src/compress/flate/inflate.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ type Reader interface {
267267
type decompressor struct {
268268
// Input source.
269269
r Reader
270+
bufR *bufio.Reader // used if provided reader does not implement io.ByteReader
270271
roffset int64
271272

272273
// Input bits, in top of b.
@@ -746,11 +747,18 @@ func (f *decompressor) huffSym(h *huffmanDecoder) (int, error) {
746747
}
747748
}
748749

749-
func makeReader(r io.Reader) Reader {
750+
func (f *decompressor) makeReader(r io.Reader) {
750751
if rr, ok := r.(Reader); ok {
751-
return rr
752+
f.bufR = nil
753+
f.r = rr
754+
return
755+
}
756+
if f.bufR != nil {
757+
f.bufR.Reset(r)
758+
} else {
759+
f.bufR = bufio.NewReader(r)
752760
}
753-
return bufio.NewReader(r)
761+
f.r = f.bufR
754762
}
755763

756764
func fixedHuffmanDecoderInit() {
@@ -775,12 +783,13 @@ func fixedHuffmanDecoderInit() {
775783

776784
func (f *decompressor) Reset(r io.Reader, dict []byte) error {
777785
*f = decompressor{
778-
r: makeReader(r),
786+
bufR: f.bufR,
779787
bits: f.bits,
780788
codebits: f.codebits,
781789
dict: f.dict,
782790
step: (*decompressor).nextBlock,
783791
}
792+
f.makeReader(r)
784793
f.dict.init(maxMatchOffset, dict)
785794
return nil
786795
}
@@ -797,7 +806,7 @@ func NewReader(r io.Reader) io.ReadCloser {
797806
fixedHuffmanDecoderInit()
798807

799808
var f decompressor
800-
f.r = makeReader(r)
809+
f.makeReader(r)
801810
f.bits = new([maxNumLit + maxNumDist]int)
802811
f.codebits = new([numCodes]int)
803812
f.step = (*decompressor).nextBlock
@@ -816,7 +825,7 @@ func NewReaderDict(r io.Reader, dict []byte) io.ReadCloser {
816825
fixedHuffmanDecoderInit()
817826

818827
var f decompressor
819-
f.r = makeReader(r)
828+
f.makeReader(r)
820829
f.bits = new([maxNumLit + maxNumDist]int)
821830
f.codebits = new([numCodes]int)
822831
f.step = (*decompressor).nextBlock

src/compress/flate/inflate_test.go

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

77
import (
8+
"bufio"
89
"bytes"
910
"io"
1011
"strings"
@@ -95,3 +96,36 @@ func TestResetDict(t *testing.T) {
9596
}
9697
}
9798
}
99+
100+
func TestReaderResetReusesReaderBuffer(t *testing.T) {
101+
encodedBuf := &bytes.Buffer{}
102+
w, _ := NewWriter(encodedBuf, 1)
103+
w.Write([]byte("abc"))
104+
w.Close()
105+
106+
encodedNotByteReader := struct{ io.Reader }{encodedBuf}
107+
108+
f := NewReader(encodedNotByteReader)
109+
bufReader1 := f.(*decompressor).r.(*bufio.Reader)
110+
f.(Resetter).Reset(encodedNotByteReader, nil)
111+
bufReader2 := f.(*decompressor).r.(*bufio.Reader)
112+
if bufReader1 != bufReader2 {
113+
t.Fatalf("bufio.Reader was not reused")
114+
}
115+
f.(Resetter).Reset(encodedBuf, nil)
116+
bufReader3 := f.(*decompressor).r
117+
if bufReader3 != encodedBuf {
118+
t.Fatalf("bufio.Reader was reused, but should be used the provided io.ByteReader directly instead")
119+
}
120+
f.(Resetter).Reset(encodedNotByteReader, nil)
121+
_, ok := f.(*decompressor).r.(*bufio.Reader)
122+
if !ok {
123+
t.Fatalf("new bufio.Reader should be created")
124+
}
125+
f.Close()
126+
f = NewReader(encodedBuf)
127+
bufReader4 := f.(*decompressor).r
128+
if bufReader4 != encodedBuf {
129+
t.Fatalf("bufio.Reader implementing io.ByteReader was not used directly")
130+
}
131+
}

0 commit comments

Comments
 (0)