bytes: optimize Buffer's Write, WriteString, WriteByte, and WriteRune

In the common case, the grow method only needs to reslice the internal
buffer. Making another function call to grow can be expensive when Write
is called very often with small pieces of data (like a byte or rune).
Thus, we add a tryGrowByReslice method that is inlineable so that we can
avoid an extra call in most cases.

name                       old time/op    new time/op    delta
WriteByte-4                  35.5µs ± 0%    17.4µs ± 1%   -51.03%  (p=0.000 n=19+20)
WriteRune-4                  55.7µs ± 1%    38.7µs ± 1%   -30.56%  (p=0.000 n=18+19)
BufferNotEmptyWriteRead-4     304µs ± 5%     283µs ± 3%    -6.86%  (p=0.000 n=19+17)
BufferFullSmallReads-4       87.0µs ± 5%    66.8µs ± 2%   -23.26%  (p=0.000 n=17+17)

name                       old speed      new speed      delta
WriteByte-4                 115MB/s ± 0%   235MB/s ± 1%  +104.19%  (p=0.000 n=19+20)
WriteRune-4                 221MB/s ± 1%   318MB/s ± 1%   +44.01%  (p=0.000 n=18+19)

Fixes #17857

Change-Id: I08dfb10a1c7e001817729dbfcc951bda12fe8814
Reviewed-on: https://go-review.googlesource.com/42813
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Marvin Stenger 2017-05-07 10:43:17 +02:00 committed by Brad Fitzpatrick
parent 23c5db9bbb
commit c08ac36761
2 changed files with 80 additions and 24 deletions

View file

@ -6,8 +6,10 @@ package bytes_test
import (
. "bytes"
"internal/testenv"
"io"
"math/rand"
"os/exec"
"runtime"
"testing"
"unicode/utf8"
@ -546,6 +548,33 @@ func TestBufferGrowth(t *testing.T) {
}
}
// Test that tryGrowByReslice is inlined.
func TestTryGrowByResliceInlined(t *testing.T) {
t.Parallel()
goBin := testenv.GoToolPath(t)
out, err := exec.Command(goBin, "tool", "nm", goBin).CombinedOutput()
if err != nil {
t.Fatalf("go tool nm: %v: %s", err, out)
}
// Verify this doesn't exist:
sym := "bytes.(*Buffer).tryGrowByReslice"
if Contains(out, []byte(sym)) {
t.Errorf("found symbol %q in cmd/go, but should be inlined", sym)
}
}
func BenchmarkWriteByte(b *testing.B) {
const n = 4 << 10
b.SetBytes(n)
buf := NewBuffer(make([]byte, n))
for i := 0; i < b.N; i++ {
buf.Reset()
for i := 0; i < n; i++ {
buf.WriteByte('x')
}
}
}
func BenchmarkWriteRune(b *testing.B) {
const n = 4 << 10
const r = '☺'