Skip to content

Commit 9c3630f

Browse files
committed
compress/flate: avoid large stack growth in fillDeflate
Ranging over an array causes the array to be copied over to the stack, which cause large re-growths. Instead, we should iterate over slices of the array. Also, assigning a large struct literal uses the stack even though the actual fields being populated are small in comparison to the entirety of the struct (see #18636). Fixing the stack growth does not alter CPU-time performance much since the stack-growth and copying was such a tiny portion of the compression work: name old time/op new time/op delta Encode/Digits/Default/1e4-8 332µs ± 1% 332µs ± 1% ~ (p=0.796 n=10+10) Encode/Digits/Default/1e5-8 5.07ms ± 2% 5.05ms ± 1% ~ (p=0.815 n=9+8) Encode/Digits/Default/1e6-8 53.7ms ± 1% 53.9ms ± 1% ~ (p=0.075 n=10+10) Encode/Twain/Default/1e4-8 380µs ± 1% 380µs ± 1% ~ (p=0.684 n=10+10) Encode/Twain/Default/1e5-8 5.79ms ± 2% 5.79ms ± 1% ~ (p=0.497 n=9+10) Encode/Twain/Default/1e6-8 61.5ms ± 1% 61.8ms ± 1% ~ (p=0.247 n=10+10) name old speed new speed delta Encode/Digits/Default/1e4-8 30.1MB/s ± 1% 30.1MB/s ± 1% ~ (p=0.753 n=10+10) Encode/Digits/Default/1e5-8 19.7MB/s ± 2% 19.8MB/s ± 1% ~ (p=0.795 n=9+8) Encode/Digits/Default/1e6-8 18.6MB/s ± 1% 18.5MB/s ± 1% ~ (p=0.072 n=10+10) Encode/Twain/Default/1e4-8 26.3MB/s ± 1% 26.3MB/s ± 1% ~ (p=0.616 n=10+10) Encode/Twain/Default/1e5-8 17.3MB/s ± 2% 17.3MB/s ± 1% ~ (p=0.484 n=9+10) Encode/Twain/Default/1e6-8 16.3MB/s ± 1% 16.2MB/s ± 1% ~ (p=0.238 n=10+10) Updates #18636 Fixes #18625 Change-Id: I471b20339bf675f63dc56d38b3acdd824fe23328 Reviewed-on: https://go-review.googlesource.com/35122 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Joe Tsai <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 4f0aac5 commit 9c3630f

File tree

3 files changed

+53
-4
lines changed

3 files changed

+53
-4
lines changed

src/compress/flate/deflate.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,17 @@ func (d *compressor) fillDeflate(b []byte) int {
136136
delta := d.hashOffset - 1
137137
d.hashOffset -= delta
138138
d.chainHead -= delta
139-
for i, v := range d.hashPrev {
139+
140+
// Iterate over slices instead of arrays to avoid copying
141+
// the entire table onto the stack (Issue #18625).
142+
for i, v := range d.hashPrev[:] {
140143
if int(v) > delta {
141144
d.hashPrev[i] = uint32(int(v) - delta)
142145
} else {
143146
d.hashPrev[i] = 0
144147
}
145148
}
146-
for i, v := range d.hashHead {
149+
for i, v := range d.hashHead[:] {
147150
if int(v) > delta {
148151
d.hashHead[i] = uint32(int(v) - delta)
149152
} else {

src/compress/flate/deflate_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io"
1313
"io/ioutil"
1414
"reflect"
15+
"runtime/debug"
1516
"sync"
1617
"testing"
1718
)
@@ -864,3 +865,33 @@ func TestBestSpeedMaxMatchOffset(t *testing.T) {
864865
}
865866
}
866867
}
868+
869+
func TestMaxStackSize(t *testing.T) {
870+
// This test must not run in parallel with other tests as debug.SetMaxStack
871+
// affects all goroutines.
872+
n := debug.SetMaxStack(1 << 16)
873+
defer debug.SetMaxStack(n)
874+
875+
var wg sync.WaitGroup
876+
defer wg.Wait()
877+
878+
b := make([]byte, 1<<20)
879+
for level := HuffmanOnly; level <= BestCompression; level++ {
880+
// Run in separate goroutine to increase probability of stack regrowth.
881+
wg.Add(1)
882+
go func(level int) {
883+
defer wg.Done()
884+
zw, err := NewWriter(ioutil.Discard, level)
885+
if err != nil {
886+
t.Errorf("level %d, NewWriter() = %v, want nil", level, err)
887+
}
888+
if n, err := zw.Write(b); n != len(b) || err != nil {
889+
t.Errorf("level %d, Write() = (%d, %v), want (%d, nil)", level, n, err, len(b))
890+
}
891+
if err := zw.Close(); err != nil {
892+
t.Errorf("level %d, Close() = %v, want nil", level, err)
893+
}
894+
zw.Reset(ioutil.Discard)
895+
}(level)
896+
}
897+
}

src/compress/flate/deflatefast.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func newDeflateFast() *deflateFast {
6060
func (e *deflateFast) encode(dst []token, src []byte) []token {
6161
// Ensure that e.cur doesn't wrap.
6262
if e.cur > 1<<30 {
63-
*e = deflateFast{cur: maxStoreBlockSize, prev: e.prev[:0]}
63+
e.resetAll()
6464
}
6565

6666
// This check isn't in the Snappy implementation, but there, the caller
@@ -265,6 +265,21 @@ func (e *deflateFast) reset() {
265265

266266
// Protect against e.cur wraparound.
267267
if e.cur > 1<<30 {
268-
*e = deflateFast{cur: maxStoreBlockSize, prev: e.prev[:0]}
268+
e.resetAll()
269+
}
270+
}
271+
272+
// resetAll resets the deflateFast struct and is only called in rare
273+
// situations to prevent integer overflow. It manually resets each field
274+
// to avoid causing large stack growth.
275+
//
276+
// See https://golang.org/issue/18636.
277+
func (e *deflateFast) resetAll() {
278+
// This is equivalent to:
279+
// *e = deflateFast{cur: maxStoreBlockSize, prev: e.prev[:0]}
280+
e.cur = maxStoreBlockSize
281+
e.prev = e.prev[:0]
282+
for i := range e.table {
283+
e.table[i] = tableEntry{}
269284
}
270285
}

0 commit comments

Comments
 (0)