diff --git a/src/bytes/buffer.go b/src/bytes/buffer.go index 549b077708..0bacbda164 100644 --- a/src/bytes/buffer.go +++ b/src/bytes/buffer.go @@ -138,10 +138,8 @@ func (b *Buffer) grow(n int) int { } else if c > maxInt-c-n { panic(ErrTooLarge) } else { - // Not enough space anywhere, we need to allocate. - buf := makeSlice(2*c + n) - copy(buf, b.buf[b.off:]) - b.buf = buf + // Add b.off to account for b.buf[:b.off] being sliced off the front. + b.buf = growSlice(b.buf[b.off:], b.off+n) } // Restore b.off and len(b.buf). b.off = 0 @@ -217,16 +215,31 @@ func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) { } } -// makeSlice allocates a slice of size n. If the allocation fails, it panics -// with ErrTooLarge. -func makeSlice(n int) []byte { - // If the make fails, give a known error. +// growSlice grows b by n, preserving the original content of b. +// If the allocation fails, it panics with ErrTooLarge. +func growSlice(b []byte, n int) []byte { defer func() { if recover() != nil { panic(ErrTooLarge) } }() - return make([]byte, n) + // TODO(http://golang.org/issue/51462): We should rely on the append-make + // pattern so that the compiler can call runtime.growslice. For example: + // return append(b, make([]byte, n)...) + // This avoids unnecessary zero-ing of the first len(b) bytes of the + // allocated slice, but this pattern causes b to escape onto the heap. + // + // Instead use the append-make pattern with a nil slice to ensure that + // we allocate buffers rounded up to the closest size class. + c := len(b) + n // ensure enough space for n elements + if c < 2*cap(b) { + // The growth rate has historically always been 2x. In the future, + // we could rely purely on append to determine the growth rate. + c = 2 * cap(b) + } + b2 := append([]byte(nil), make([]byte, c)...) + copy(b2, b) + return b2[:len(b)] } // WriteTo writes data to w until the buffer is drained or an error occurs. diff --git a/src/bytes/buffer_test.go b/src/bytes/buffer_test.go index 9c9b7440ff..c0855007c1 100644 --- a/src/bytes/buffer_test.go +++ b/src/bytes/buffer_test.go @@ -672,3 +672,18 @@ func BenchmarkBufferFullSmallReads(b *testing.B) { } } } + +func BenchmarkBufferWriteBlock(b *testing.B) { + block := make([]byte, 1024) + for _, n := range []int{1 << 12, 1 << 16, 1 << 20} { + b.Run(fmt.Sprintf("N%d", n), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + var bb Buffer + for bb.Len() < n { + bb.Write(block) + } + } + }) + } +}