Skip to content

Commit 7d7fd6d

Browse files
neildgopherbot
authored andcommitted
io: allocate copy buffers from a pool
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 <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Thomas Austad <[email protected]> Auto-Submit: Damien Neil <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent adc1db2 commit 7d7fd6d

File tree

2 files changed

+15
-32
lines changed

2 files changed

+15
-32
lines changed

src/io/io.go

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,13 @@ func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
400400
return copyBuffer(dst, src, buf)
401401
}
402402

403+
var bufPool = sync.Pool{
404+
New: func() any {
405+
b := make([]byte, 32*1024)
406+
return &b
407+
},
408+
}
409+
403410
// copyBuffer is the actual implementation of Copy and CopyBuffer.
404411
// if buf is nil, one is allocated.
405412
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) {
413420
return rt.ReadFrom(src)
414421
}
415422
if buf == nil {
416-
size := 32 * 1024
417-
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
418-
if l.N < 1 {
419-
size = 1
420-
} else {
421-
size = int(l.N)
422-
}
423-
}
424-
buf = make([]byte, size)
423+
bufp := bufPool.Get().(*[]byte)
424+
defer bufPool.Put(bufp)
425+
buf = *bufp
425426
}
426427
for {
427428
nr, er := src.Read(buf)
@@ -637,21 +638,14 @@ func (discard) WriteString(s string) (int, error) {
637638
return len(s), nil
638639
}
639640

640-
var blackHolePool = sync.Pool{
641-
New: func() any {
642-
b := make([]byte, 8192)
643-
return &b
644-
},
645-
}
646-
647641
func (discard) ReadFrom(r Reader) (n int64, err error) {
648-
bufp := blackHolePool.Get().(*[]byte)
642+
bufp := bufPool.Get().(*[]byte)
649643
readSize := 0
650644
for {
651645
readSize, err = r.Read(*bufp)
652646
n += int64(readSize)
653647
if err != nil {
654-
blackHolePool.Put(bufp)
648+
bufPool.Put(bufp)
655649
if err == EOF {
656650
return n, nil
657651
}

src/net/http/server.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -567,24 +567,20 @@ type writerOnly struct {
567567
// to a *net.TCPConn with sendfile, or from a supported src type such
568568
// as a *net.TCPConn on Linux with splice.
569569
func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
570-
bufp := copyBufPool.Get().(*[]byte)
571-
buf := *bufp
572-
defer copyBufPool.Put(bufp)
573-
574570
// Our underlying w.conn.rwc is usually a *TCPConn (with its
575571
// own ReadFrom method). If not, just fall back to the normal
576572
// copy method.
577573
rf, ok := w.conn.rwc.(io.ReaderFrom)
578574
if !ok {
579-
return io.CopyBuffer(writerOnly{w}, src, buf)
575+
return io.Copy(writerOnly{w}, src)
580576
}
581577

582578
// Copy the first sniffLen bytes before switching to ReadFrom.
583579
// This ensures we don't start writing the response before the
584580
// source is available (see golang.org/issue/5660) and provides
585581
// enough bytes to perform Content-Type sniffing when required.
586582
if !w.cw.wroteHeader {
587-
n0, err := io.CopyBuffer(writerOnly{w}, io.LimitReader(src, sniffLen), buf)
583+
n0, err := io.Copy(writerOnly{w}, io.LimitReader(src, sniffLen))
588584
n += n0
589585
if err != nil || n0 < sniffLen {
590586
return n, err
@@ -602,7 +598,7 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
602598
return n, err
603599
}
604600

605-
n0, err := io.CopyBuffer(writerOnly{w}, src, buf)
601+
n0, err := io.Copy(writerOnly{w}, src)
606602
n += n0
607603
return n, err
608604
}
@@ -799,13 +795,6 @@ var (
799795
bufioWriter4kPool sync.Pool
800796
)
801797

802-
var copyBufPool = sync.Pool{
803-
New: func() any {
804-
b := make([]byte, 32*1024)
805-
return &b
806-
},
807-
}
808-
809798
func bufioWriterPool(size int) *sync.Pool {
810799
switch size {
811800
case 2 << 10:

0 commit comments

Comments
 (0)