Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/http: reuse gzip reader from pool for transport #61353

Open
Sunyue opened this issue Jul 14, 2023 · 5 comments
Open

net/http: reuse gzip reader from pool for transport #61353

Sunyue opened this issue Jul 14, 2023 · 5 comments
Labels
help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Milestone

Comments

@Sunyue
Copy link

Sunyue commented Jul 14, 2023

gz.zr, gz.zerr = gzip.NewReader(gz.body)

Is it possible to reuse this gzip reader from a sync.Pool to reduce the memory allocation cause by flate.NewReader

@Sunyue Sunyue changed the title Reuse gzip reader from pool net/http/transport.go: Is it possible to reuse gzip reader from pool Jul 14, 2023
@bcmills bcmills added Performance NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Jul 14, 2023
@bcmills bcmills added this to the Backlog milestone Jul 14, 2023
@bcmills
Copy link
Contributor

bcmills commented Jul 14, 2023

gzip.Reader does have a Reset method, but we would also need some way to know when the gzip.Reader is safe to reuse.

Hmm. (*http.gzipReader).Read already has some locking in Read. Perhaps we could do something like:

func (gz *gzipReader) Read(p []byte) (n int, err error) {
	gz.body.mu.Lock()
	if gz.body.closed {
		err = errReadOnClosedResBody
	} else if gz.zr == nil {
		if gz.zerr == nil {
			zr := gzipPool.Get().(*gzip.Reader)
			gz.zerr = zr.Reset(gz.body)
			if gz.zerr == nil {
				gz.zr = zr
			} else {
				gzipPool.Put(zr)
			}
		}
		err = gz.zerr
	}
	gz.body.mu.Unlock()

	if err != nil {
		return 0, err
	}
	return gz.zr.Read(p)
}

func (gz *gzipReader) Close() error {
	err := gz.body.Close()

	gz.body.mu.Lock()
	if gz.zr != nil {
		gz.zr.Reset(emptyReader{})  // drop reference to gz.body
		gzipPool.Put(gz.zr)
		gz.zr = nil
	}
	gz.body.mu.Unlock()

	return err
}

var (
	gzipPool = sync.Pool{
		New: func() any { return new(gzip.Reader) },
	}
	_ flate.Reader = emptyReader{}
)

type emptyReader struct{}

func (emptyReader) Read([]byte) (int, error) { return 0, io.EOF }
func (emptyReader) ReadByte() (byte, error) { return 0, io.EOF }

Of course, such a change would also need a supporting benchmark. @Sunyue, want to send it for Go 1.22?

(CC @neild)

@neild
Copy link
Contributor

neild commented Jul 14, 2023

Seems like something we could do; I'm happy to review a CL if someone wants to send me one.

AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 16, 2023
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 16, 2023
name              old time/op    new time/op    delta
GzipReaderInit-8    9.15µs ± 4%    2.38µs ± 9%  -74.04%  (p=0.000 n=10+10)

name              old alloc/op   new alloc/op   delta
GzipReaderInit-8    45.5kB ± 0%     4.3kB ± 0%  -90.47%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
GzipReaderInit-8      10.0 ± 0%       5.0 ± 0%  -50.00%  (p=0.000 n=10+10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Fixes golang#61353
@gopherbot
Copy link

Change https://go.dev/cl/510115 mentions this issue: net/http: pool transport gzip readers

AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 17, 2023
name              old time/op    new time/op    delta
GzipReaderInit-8    8.97µs ± 6%    2.61µs ± 7%  -70.87%  (p=0.000 n=10+9)

name              old alloc/op   new alloc/op   delta
GzipReaderInit-8    45.5kB ± 0%     4.4kB ± 0%  -90.36%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
GzipReaderInit-8      10.0 ± 0%       6.0 ± 0%  -40.00%  (p=0.000 n=10+10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Fixes golang#61353
@gopherbot
Copy link

Change https://go.dev/cl/510255 mentions this issue: net/http: pool transport gzip readers

AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 17, 2023
name              old time/op    new time/op    delta
GzipReaderInit-8    8.97µs ± 6%    2.61µs ± 7%  -70.87%  (p=0.000 n=10+9)

name              old alloc/op   new alloc/op   delta
GzipReaderInit-8    45.5kB ± 0%     4.4kB ± 0%  -90.36%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
GzipReaderInit-8      10.0 ± 0%       6.0 ± 0%  -40.00%  (p=0.000 n=10+10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Fixes golang#61353
@AlexanderYastrebov
Copy link
Contributor

Created #61390

AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 18, 2023
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 18, 2023
                 │     old     │                 new                 │
                 │   sec/op    │   sec/op     vs base                │
GzipReaderInit-8   8.940µ ± 4%   2.532µ ± 8%  -71.68% (p=0.000 n=10)

                 │      old      │                 new                  │
                 │     B/op      │     B/op      vs base                │
GzipReaderInit-8   44.438Ki ± 0%   4.297Ki ± 0%  -90.33% (p=0.000 n=10)

                 │     old     │                new                 │
                 │  allocs/op  │ allocs/op   vs base                │
GzipReaderInit-8   10.000 ± 0%   6.000 ± 0%  -40.00% (p=0.000 n=10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Updates golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 18, 2023
                 │     old     │                 new                  │
                 │   sec/op    │    sec/op     vs base                │
GzipReaderInit-8   8.854µ ± 5%   2.578µ ± 16%  -70.89% (p=0.000 n=10)

                 │      old      │                 new                  │
                 │     B/op      │     B/op      vs base                │
GzipReaderInit-8   44.438Ki ± 0%   4.282Ki ± 0%  -90.36% (p=0.000 n=10)

                 │     old     │                new                 │
                 │  allocs/op  │ allocs/op   vs base                │
GzipReaderInit-8   10.000 ± 0%   6.000 ± 0%  -40.00% (p=0.000 n=10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Updates golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 19, 2023
                 │     old     │                 new                  │
                 │   sec/op    │    sec/op     vs base                │
GzipReaderInit-8   8.854µ ± 5%   2.578µ ± 16%  -70.89% (p=0.000 n=10)

                 │      old      │                 new                  │
                 │     B/op      │     B/op      vs base                │
GzipReaderInit-8   44.438Ki ± 0%   4.282Ki ± 0%  -90.36% (p=0.000 n=10)

                 │     old     │                new                 │
                 │  allocs/op  │ allocs/op   vs base                │
GzipReaderInit-8   10.000 ± 0%   6.000 ± 0%  -40.00% (p=0.000 n=10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Updates golang#61353
@seankhliao seankhliao changed the title net/http/transport.go: Is it possible to reuse gzip reader from pool net/http: reuse gzip reader from pool for transport Jul 19, 2023
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 19, 2023
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 19, 2023
                 │     old      │                 new                 │
                 │    sec/op    │   sec/op     vs base                │
GzipReaderInit-8   8.891µ ± 10%   2.570µ ± 8%  -71.10% (p=0.000 n=10)

                 │      old      │                 new                  │
                 │     B/op      │     B/op      vs base                │
GzipReaderInit-8   44.438Ki ± 0%   4.282Ki ± 0%  -90.36% (p=0.000 n=10)

                 │     old     │                new                 │
                 │  allocs/op  │ allocs/op   vs base                │
GzipReaderInit-8   10.000 ± 0%   6.000 ± 0%  -40.00% (p=0.000 n=10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Updates golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 19, 2023
                 │     old     │                 new                  │
                 │   sec/op    │    sec/op     vs base                │
GzipReaderInit-8   8.854µ ± 5%   2.578µ ± 16%  -70.89% (p=0.000 n=10)

                 │      old      │                 new                  │
                 │     B/op      │     B/op      vs base                │
GzipReaderInit-8   44.438Ki ± 0%   4.282Ki ± 0%  -90.36% (p=0.000 n=10)

                 │     old     │                new                 │
                 │  allocs/op  │ allocs/op   vs base                │
GzipReaderInit-8   10.000 ± 0%   6.000 ± 0%  -40.00% (p=0.000 n=10)

Allocation saving comes from absent compress/flate.(*dictDecoder).init

Updates golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 21, 2023
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 21, 2023
             │     old     │              new               │
             │   sec/op    │    sec/op     vs base          │
ClientGzip-8   649.4µ ± 4%   644.4µ ± 12%  ~ (p=0.739 n=10)

             │      old      │                 new                  │
             │     B/op      │     B/op      vs base                │
ClientGzip-8   49.486Ki ± 0%   9.270Ki ± 2%  -81.27% (p=0.000 n=10)

             │    old     │                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 golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 21, 2023
             │     old     │              new               │
             │   sec/op    │    sec/op     vs base          │
ClientGzip-8   649.4µ ± 4%   644.4µ ± 12%  ~ (p=0.739 n=10)

             │      old      │                 new                  │
             │     B/op      │     B/op      vs base                │
ClientGzip-8   49.486Ki ± 0%   9.270Ki ± 2%  -81.27% (p=0.000 n=10)

             │    old     │                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 golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Jul 21, 2023
             │     old     │              new               │
             │   sec/op    │    sec/op     vs base          │
ClientGzip-8   649.4µ ± 4%   644.4µ ± 12%  ~ (p=0.739 n=10)

             │      old      │                 new                  │
             │     B/op      │     B/op      vs base                │
ClientGzip-8   49.486Ki ± 0%   9.270Ki ± 2%  -81.27% (p=0.000 n=10)

             │    old     │                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 golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Sep 23, 2023
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Sep 23, 2023
             │     old     │              new               │
             │   sec/op    │    sec/op     vs base          │
ClientGzip-8   649.4µ ± 4%   644.4µ ± 12%  ~ (p=0.739 n=10)

             │      old      │                 new                  │
             │     B/op      │     B/op      vs base                │
ClientGzip-8   49.486Ki ± 0%   9.270Ki ± 2%  -81.27% (p=0.000 n=10)

             │    old     │                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 golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Sep 23, 2023
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 golang#61353
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Nov 10, 2023
AlexanderYastrebov added a commit to AlexanderYastrebov/go that referenced this issue Nov 10, 2023
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 golang#61353
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants