]> Cypherpunks repositories - gostls13.git/commitdiff
io: allocate copy buffers from a pool
authorDamien Neil <dneil@google.com>
Fri, 9 Dec 2022 17:56:15 +0000 (09:56 -0800)
committerGopher Robot <gobot@golang.org>
Tue, 31 Jan 2023 17:44:33 +0000 (17:44 +0000)
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 <gobot@golang.org>
Run-TryBot: Damien Neil <dneil@google.com>
Reviewed-by: Thomas Austad <thomas.austad@gmail.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
src/io/io.go
src/net/http/server.go

index 630ab73b56f4f18b091d9284b3b06377a3d36ecd..374e20bf8c602233a8fff2c4ea654c692fafc04c 100644 (file)
@@ -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
                        }
index c15f0f58cb76cf27aa46cd70250ab0ca2656b026..bb31761ade838957052b1c137ba21b2e87a656d2 100644 (file)
@@ -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: