} 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
}
}
-// 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.
}
}
}
+
+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)
+ }
+ }
+ })
+ }
+}