Commit 4c0e51cd authored by Rob Pike's avatar Rob Pike

Make printing faster by avoiding mallocs and some other advances.

Roughly 33% faster for simple cases, probably more for complex ones.

Before:

mallocs per Sprintf(""): 4
mallocs per Sprintf("xxx"): 6
mallocs per Sprintf("%x"): 10
mallocs per Sprintf("%x %x"): 12

Now:

mallocs per Sprintf(""): 2
mallocs per Sprintf("xxx"): 3
mallocs per Sprintf("%x"): 5
mallocs per Sprintf("%x %x"): 7

Speed improves because of avoiding mallocs and also by sharing a bytes.Buffer
between print.go and format.go rather than copying the data back after each
printed item.

Before:

fmt_test.BenchmarkSprintfEmpty	1000000	      1346 ns/op
fmt_test.BenchmarkSprintfString	500000	      3461 ns/op
fmt_test.BenchmarkSprintfInt	500000	      3671 ns/op

Now:

fmt_test.BenchmarkSprintfEmpty	 2000000	       995 ns/op
fmt_test.BenchmarkSprintfString	 1000000	      2745 ns/op
fmt_test.BenchmarkSprintfInt	 1000000	      2391 ns/op
fmt_test.BenchmarkSprintfIntInt	  500000	      3751 ns/op

I believe there is more to get but this is a good milestone.

R=rsc
CC=golang-dev, hong
https://golang.org/cl/166076
parent ed6fd1bc
......@@ -35,6 +35,7 @@ type Buffer struct {
buf []byte; // contents are the bytes buf[off : len(buf)]
off int; // read at &buf[off], write at &buf[len(buf)]
oneByte [1]byte; // avoid allocation of slice on each WriteByte
bootstrap [64]byte; // memory to hold first slice; helps small buffers (Printf) avoid allocation.
}
// Bytes returns the contents of the unread portion of the buffer;
......@@ -69,29 +70,51 @@ func (b *Buffer) Truncate(n int) {
// b.Reset() is the same as b.Truncate(0).
func (b *Buffer) Reset() { b.Truncate(0) }
// Write appends the contents of p to the buffer. The return
// value n is the length of p; err is always nil.
func (b *Buffer) Write(p []byte) (n int, err os.Error) {
m := b.Len();
n = len(p);
// Resize buffer to guarantee enough space for n more bytes.
// After this call, the state of b.buf is inconsistent.
// It must be fixed up as is done in Write and WriteString.
func (b *Buffer) resize(n int) {
var buf []byte;
if b.buf == nil && n <= len(b.bootstrap) {
buf = &b.bootstrap
} else {
buf = b.buf;
if len(b.buf)+n > cap(b.buf) {
// not enough space at end
buf := b.buf;
if m+n > cap(b.buf) {
// not enough space anywhere
buf = make([]byte, 2*cap(b.buf)+n)
}
copyBytes(buf, 0, b.buf[b.off:b.off+m]);
copy(buf, b.buf[b.off:]);
}
b.buf = buf;
b.off = 0;
}
}
// Write appends the contents of p to the buffer. The return
// value n is the length of p; err is always nil.
func (b *Buffer) Write(p []byte) (n int, err os.Error) {
m := b.Len();
n = len(p);
if len(b.buf)+n > cap(b.buf) {
b.resize(n)
}
b.buf = b.buf[0 : b.off+m+n];
copyBytes(b.buf, b.off+m, p);
return n, nil;
}
// WriteString appends the contents of s to the buffer. The return
// value n is the length of s; err is always nil.
func (b *Buffer) WriteString(s string) (n int, err os.Error) {
m := b.Len();
n = len(s);
if len(b.buf)+n > cap(b.buf) {
b.resize(n)
}
b.buf = b.buf[0 : b.off+m+n];
copyString(b.buf, b.off+m, s);
return n, nil;
}
// MinRead is the minimum slice size passed to a Read call by
// Buffer.ReadFrom. As long as the Buffer has at least MinRead bytes beyond
// what is required to hold the contents of r, ReadFrom will not grow the
......@@ -146,29 +169,6 @@ func (b *Buffer) WriteTo(w io.Writer) (n int64, err os.Error) {
return;
}
// WriteString appends the contents of s to the buffer. The return
// value n is the length of s; err is always nil.
func (b *Buffer) WriteString(s string) (n int, err os.Error) {
m := b.Len();
n = len(s);
if len(b.buf)+n > cap(b.buf) {
// not enough space at end
buf := b.buf;
if m+n > cap(b.buf) {
// not enough space anywhere
buf = make([]byte, 2*cap(b.buf)+n)
}
copyBytes(buf, 0, b.buf[b.off:b.off+m]);
b.buf = buf;
b.off = 0;
}
b.buf = b.buf[0 : b.off+m+n];
copyString(b.buf, b.off+m, s);
return n, nil;
}
// WriteByte appends the byte c to the buffer.
// The returned error is always nil, but is included
// to match bufio.Writer's WriteByte.
......
......@@ -7,6 +7,7 @@ package fmt_test
import (
. "fmt";
"io";
"malloc"; // for the malloc count test only
"math";
"strings";
"testing";
......@@ -242,7 +243,7 @@ func TestSprintf(t *testing.T) {
if _, ok := tt.val.(string); ok {
// Don't requote the already-quoted strings.
// It's too confusing to read the errors.
t.Errorf("Sprintf(%q, %q) = %s want %s", tt.fmt, tt.val, s, tt.out)
t.Errorf("Sprintf(%q, %q) = <%s> want <%s>", tt.fmt, tt.val, s, tt.out)
} else {
t.Errorf("Sprintf(%q, %v) = %q want %q", tt.fmt, tt.val, s, tt.out)
}
......@@ -268,6 +269,39 @@ func BenchmarkSprintfInt(b *testing.B) {
}
}
func BenchmarkSprintfIntInt(b *testing.B) {
for i := 0; i < b.N; i++ {
Sprintf("%d %d", 5, 6)
}
}
func TestCountMallocs(t *testing.T) {
mallocs := 0 - malloc.GetStats().Mallocs;
for i := 0; i < 100; i++ {
Sprintf("")
}
mallocs += malloc.GetStats().Mallocs;
Printf("mallocs per Sprintf(\"\"): %d\n", mallocs/100);
mallocs = 0 - malloc.GetStats().Mallocs;
for i := 0; i < 100; i++ {
Sprintf("xxx")
}
mallocs += malloc.GetStats().Mallocs;
Printf("mallocs per Sprintf(\"xxx\"): %d\n", mallocs/100);
mallocs = 0 - malloc.GetStats().Mallocs;
for i := 0; i < 100; i++ {
Sprintf("%x", i)
}
mallocs += malloc.GetStats().Mallocs;
Printf("mallocs per Sprintf(\"%%x\"): %d\n", mallocs/100);
mallocs = 0 - malloc.GetStats().Mallocs;
for i := 0; i < 100; i++ {
Sprintf("%x %x", i, i)
}
mallocs += malloc.GetStats().Mallocs;
Printf("mallocs per Sprintf(\"%%x %%x\"): %d\n", mallocs/100);
}
type flagPrinter struct{}
func (*flagPrinter) Format(f State, c int) {
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment