From 7d7fd6d3627f7dbba250ecfecea2f5ffe6c5aaa7 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Fri, 9 Dec 2022 09:56:15 -0800 Subject: [PATCH] io: allocate copy buffers from a pool MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit CopyBuffer allocates a 32k buffer when no buffer is available. Allocate these buffers from a sync.Pool. This removes an optimization where the copy buffer size was reduced when the source is a io.LimitedReader (including the case of CopyN) with a limit less than the default buffer size. This change could cause a program which only uses io.Copy with sources with a small limit to allocate unnecessarily large buffers. Programs which care about the transient buffer allocation can avoid this by providing their own buffer. name old time/op new time/op delta CopyNSmall-10 165ns ± 7% 117ns ± 7% -29.19% (p=0.001 n=7+7) CopyNLarge-10 7.33µs ±34% 4.07µs ± 2% -44.52% (p=0.001 n=7+7) name old alloc/op new alloc/op delta CopyNSmall-10 2.20kB ±12% 1.20kB ± 4% -45.24% (p=0.000 n=8+7) CopyNLarge-10 148kB ± 9% 81kB ± 4% -45.26% (p=0.000 n=8+7) name old allocs/op new allocs/op delta CopyNSmall-10 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=8+8) CopyNLarge-10 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=8+8) For #57202 Change-Id: I2292226da9ba1dc09a2543f5d74fe5da06080d49 Reviewed-on: https://go-review.googlesource.com/c/go/+/456555 TryBot-Result: Gopher Robot Run-TryBot: Damien Neil Reviewed-by: Thomas Austad Auto-Submit: Damien Neil Reviewed-by: Ian Lance Taylor --- src/io/io.go | 30 ++++++++++++------------------ src/net/http/server.go | 17 +++-------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/io/io.go b/src/io/io.go index 630ab73b56..374e20bf8c 100644 --- a/src/io/io.go +++ b/src/io/io.go @@ -400,6 +400,13 @@ func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) { return copyBuffer(dst, src, buf) } +var bufPool = sync.Pool{ + New: func() any { + b := make([]byte, 32*1024) + return &b + }, +} + // copyBuffer is the actual implementation of Copy and CopyBuffer. // if buf is nil, one is allocated. func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) { @@ -413,15 +420,9 @@ func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) { return rt.ReadFrom(src) } if buf == nil { - size := 32 * 1024 - if l, ok := src.(*LimitedReader); ok && int64(size) > l.N { - if l.N < 1 { - size = 1 - } else { - size = int(l.N) - } - } - buf = make([]byte, size) + bufp := bufPool.Get().(*[]byte) + defer bufPool.Put(bufp) + buf = *bufp } for { nr, er := src.Read(buf) @@ -637,21 +638,14 @@ func (discard) WriteString(s string) (int, error) { return len(s), nil } -var blackHolePool = sync.Pool{ - New: func() any { - b := make([]byte, 8192) - return &b - }, -} - func (discard) ReadFrom(r Reader) (n int64, err error) { - bufp := blackHolePool.Get().(*[]byte) + bufp := bufPool.Get().(*[]byte) readSize := 0 for { readSize, err = r.Read(*bufp) n += int64(readSize) if err != nil { - blackHolePool.Put(bufp) + bufPool.Put(bufp) if err == EOF { return n, nil } diff --git a/src/net/http/server.go b/src/net/http/server.go index c15f0f58cb..bb31761ade 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -567,16 +567,12 @@ type writerOnly struct { // to a *net.TCPConn with sendfile, or from a supported src type such // as a *net.TCPConn on Linux with splice. func (w *response) ReadFrom(src io.Reader) (n int64, err error) { - bufp := copyBufPool.Get().(*[]byte) - buf := *bufp - defer copyBufPool.Put(bufp) - // Our underlying w.conn.rwc is usually a *TCPConn (with its // own ReadFrom method). If not, just fall back to the normal // copy method. rf, ok := w.conn.rwc.(io.ReaderFrom) if !ok { - return io.CopyBuffer(writerOnly{w}, src, buf) + return io.Copy(writerOnly{w}, src) } // Copy the first sniffLen bytes before switching to ReadFrom. @@ -584,7 +580,7 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) { // source is available (see golang.org/issue/5660) and provides // enough bytes to perform Content-Type sniffing when required. if !w.cw.wroteHeader { - n0, err := io.CopyBuffer(writerOnly{w}, io.LimitReader(src, sniffLen), buf) + n0, err := io.Copy(writerOnly{w}, io.LimitReader(src, sniffLen)) n += n0 if err != nil || n0 < sniffLen { return n, err @@ -602,7 +598,7 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) { return n, err } - n0, err := io.CopyBuffer(writerOnly{w}, src, buf) + n0, err := io.Copy(writerOnly{w}, src) n += n0 return n, err } @@ -799,13 +795,6 @@ var ( bufioWriter4kPool sync.Pool ) -var copyBufPool = sync.Pool{ - New: func() any { - b := make([]byte, 32*1024) - return &b - }, -} - func bufioWriterPool(size int) *sync.Pool { switch size { case 2 << 10: -- 2.50.0