Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8854c57

Browse files
net/http: pool transport gzip readers
goos: linux goarch: amd64 pkg: net/http │ /tmp/BenchmarkClientGzip.old │ /tmp/BenchmarkClientGzip.new │ │ sec/op │ sec/op vs base │ ClientGzip-8 509.6μ ± 7% 545.5μ ± 10% ~ (p=0.218 n=10) │ /tmp/BenchmarkClientGzip.old │ /tmp/BenchmarkClientGzip.new │ │ B/op │ B/op vs base │ ClientGzip-8 48.943Ki ± 0% 8.757Ki ± 1% -82.11% (p=0.000 n=10) │ /tmp/BenchmarkClientGzip.old │ /tmp/BenchmarkClientGzip.new │ │ allocs/op │ allocs/op vs base │ ClientGzip-8 51.00 ± 0% 46.00 ± 0% -9.80% (p=0.000 n=10) Allocation saving comes from absent compress/flate.(*dictDecoder).init Updates #61353
1 parent fd1e91c commit 8854c57

File tree

2 files changed

+81
-23
lines changed

2 files changed

+81
-23
lines changed

‎src/net/http/serve_test.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5080,13 +5080,13 @@ func getNoBody(urlStr string) (*Response, error) {
50805080
// For use like:
50815081
//
50825082
// $ go test -c
5083-
// $ ./http.test -test.run=XX -test.bench=BenchmarkClient$ -test.benchtime=15s -test.cpuprofile=http.prof
5083+
// $ ./http.test -test.run='^$' -test.bench='^BenchmarkClient$' -test.benchtime=15s -test.cpuprofile=http.prof
50845084
// $ go tool pprof http.test http.prof
50855085
// (pprof) web
50865086
func BenchmarkClient(b *testing.B) {
50875087
var data = []byte("Hello world.\n")
50885088

5089-
url := startBenchmarkServer(b, func(w ResponseWriter, r *Request) {
5089+
url := startClientBenchmarkServer(b, func(w ResponseWriter, _ *Request) {
50905090
w.Header().Set("Content-Type", "text/html; charset=utf-8")
50915091
w.Write(data)
50925092
})
@@ -5110,7 +5110,7 @@ func BenchmarkClient(b *testing.B) {
51105110
b.StopTimer()
51115111
}
51125112

5113-
func startBenchmarkServer(b *testing.B, handler func(ResponseWriter, *Request)) string {
5113+
func startClientBenchmarkServer(b *testing.B, handler func(ResponseWriter, *Request)) string {
51145114
b.ReportAllocs()
51155115
b.StopTimer()
51165116

@@ -5197,19 +5197,18 @@ func startBenchmarkServer(b *testing.B, handler func(ResponseWriter, *Request))
51975197
}
51985198

51995199
func BenchmarkClientGzip(b *testing.B) {
5200-
const nRandBytes = 1024 * 1024
5200+
const responseSize = 1024 * 1024
52015201

52025202
var buf bytes.Buffer
52035203
gz := gzip.NewWriter(&buf)
5204-
if _, err := io.CopyN(gz, crand.Reader, nRandBytes); err != nil {
5205-
fmt.Fprintln(os.Stderr, err.Error())
5206-
os.Exit(1)
5204+
if _, err := io.CopyN(gz, crand.Reader, responseSize); err != nil {
5205+
b.Fatal(err)
52075206
}
52085207
gz.Close()
52095208

52105209
data := buf.Bytes()
52115210

5212-
url := startBenchmarkServer(b, func(w ResponseWriter, r *Request) {
5211+
url := startClientBenchmarkServer(b, func(w ResponseWriter, _ *Request) {
52135212
w.Header().Set("Content-Encoding", "gzip")
52145213
w.Write(data)
52155214
})
@@ -5226,8 +5225,8 @@ func BenchmarkClientGzip(b *testing.B) {
52265225
if err != nil {
52275226
b.Fatalf("ReadAll: %v", err)
52285227
}
5229-
if n != nRandBytes {
5230-
b.Fatalf("ReadAll: expected %d, got %d", nRandBytes, n)
5228+
if n != responseSize {
5229+
b.Fatalf("ReadAll: expected %d bytes, got %d", responseSize, n)
52315230
}
52325231
}
52335232
b.StopTimer()

‎src/net/http/transport.go

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package http
1111

1212
import (
1313
"bufio"
14+
"compress/flate"
1415
"compress/gzip"
1516
"container/list"
1617
"context"
@@ -2817,6 +2818,7 @@ type bodyEOFSignal struct {
28172818
}
28182819

28192820
var errReadOnClosedResBody = errors.New("http: read on closed response body")
2821+
var errConcurrentReadOnResBody = errors.New("http: concurrent read on response body")
28202822

28212823
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
28222824
es.mu.Lock()
@@ -2866,37 +2868,94 @@ func (es *bodyEOFSignal) condfn(err error) error {
28662868
}
28672869

28682870
// gzipReader wraps a response body so it can lazily
2869-
// call gzip.NewReader on the first call to Read
2871+
// get gzip.Reader from the pool on the first call to Read.
2872+
// After Close is called it puts gzip.Reader to the pool immediately
2873+
// if there is no Read in progress or later when Read completes.
28702874
type gzipReader struct {
28712875
_ incomparable
28722876
body *bodyEOFSignal // underlying HTTP/1 response body framing
2873-
zr *gzip.Reader // lazily-initialized gzip reader
2874-
zerr error // any error from gzip.NewReader; sticky
2877+
mu sync.Mutex
2878+
zr *gzip.Reader
2879+
zerr error
28752880
}
28762881

2877-
func (gz *gzipReader) Read(p []byte) (n int, err error) {
2882+
type eofReader struct{}
2883+
2884+
func (eofReader) Read([]byte) (int, error) { return 0, io.EOF }
2885+
func (eofReader) ReadByte() (byte, error) { return 0, io.EOF }
2886+
2887+
var (
2888+
gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
2889+
2890+
// parkedReader used to reset gzip.Reader before returning into the gzipPool as
2891+
// gzip.Reader.Reset(flate.Reader) avoids calling bufio.NewReader
2892+
parkedReader flate.Reader = eofReader{}
2893+
)
2894+
2895+
func gzipPoolGet(r io.Reader) (*gzip.Reader, error) {
2896+
zr := gzipPool.Get().(*gzip.Reader)
2897+
if err := zr.Reset(r); err != nil {
2898+
gzipPoolPut(zr)
2899+
return nil, err
2900+
}
2901+
return zr, nil
2902+
}
2903+
2904+
func gzipPoolPut(zr *gzip.Reader) {
2905+
_ = zr.Reset(parkedReader)
2906+
gzipPool.Put(zr)
2907+
}
2908+
2909+
func (gz *gzipReader) get() (*gzip.Reader, error) {
2910+
gz.mu.Lock()
2911+
defer gz.mu.Unlock()
2912+
if gz.zerr != nil {
2913+
return nil, gz.zerr
2914+
}
28782915
if gz.zr == nil {
2879-
if gz.zerr == nil {
2880-
gz.zr, gz.zerr = gzip.NewReader(gz.body)
2881-
}
2916+
gz.zr, gz.zerr = gzipPoolGet(gz.body)
28822917
if gz.zerr != nil {
2883-
return 0, gz.zerr
2918+
return nil, gz.zerr
28842919
}
28852920
}
2921+
ret := gz.zr
2922+
gz.zr, gz.zerr = nil, errConcurrentReadOnResBody
2923+
return ret, nil
2924+
}
28862925

2887-
gz.body.mu.Lock()
2888-
if gz.body.closed {
2889-
err = errReadOnClosedResBody
2926+
func (gz *gzipReader) put(zr *gzip.Reader) {
2927+
gz.mu.Lock()
2928+
defer gz.mu.Unlock()
2929+
if gz.zerr == errConcurrentReadOnResBody {
2930+
gz.zr, gz.zerr = zr, nil
2931+
} else { // errReadOnClosedResBody
2932+
gzipPoolPut(zr)
28902933
}
2891-
gz.body.mu.Unlock()
2934+
}
28922935

2936+
func (gz *gzipReader) close() {
2937+
gz.mu.Lock()
2938+
defer gz.mu.Unlock()
2939+
if gz.zerr == nil && gz.zr != nil {
2940+
gzipPoolPut(gz.zr)
2941+
gz.zr = nil
2942+
}
2943+
gz.zerr = errReadOnClosedResBody
2944+
}
2945+
2946+
func (gz *gzipReader) Read(p []byte) (n int, err error) {
2947+
zr, err := gz.get()
28932948
if err != nil {
28942949
return 0, err
28952950
}
2896-
return gz.zr.Read(p)
2951+
defer gz.put(zr)
2952+
2953+
return zr.Read(p)
28972954
}
28982955

28992956
func (gz *gzipReader) Close() error {
2957+
gz.close()
2958+
29002959
return gz.body.Close()
29012960
}
29022961

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /