Last active
April 3, 2018 02:03
-
-
Save mjs/bbaf6db6b63f0e30eef1d10b0fc1fd00 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package perf_test | |
import ( | |
"bytes" | |
"fmt" | |
"strconv" | |
"testing" | |
) | |
var ( | |
v1 int64 = 1 | |
v2 int64 = 2 | |
v3 int64 = 30000000 | |
) | |
// Naive Sprintf, fairly inefficient. | |
func BenchmarkSprintf(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
var _ = []byte(fmt.Sprintf("foo a=%d,b=%d,c=%d\n", v1, v2, v3)) | |
} | |
} | |
// A buffer + Fprintf is a little faster. | |
func BenchmarkBuffer(b *testing.B) { | |
buf := new(bytes.Buffer) | |
buf.Grow(128) | |
for i := 0; i < b.N; i++ { | |
fmt.Fprintf(buf, "foo a=%d,b=%d,c=%d\n", v1, v2, v3) | |
var _ = buf.Bytes() | |
buf.Reset() | |
} | |
} | |
// Building up a []byte manually is a bit verbose but is ~39% faster | |
// than fmt.Sprintf. | |
func BenchmarkManualAppend(b *testing.B) { | |
// A bit verbose, but quite fast. | |
buf := make([]byte, 0, 128) | |
for i := 0; i < b.N; i++ { | |
buf = append(buf, []byte("foo a=")...) | |
buf = strconv.AppendInt(buf, v1, 10) | |
buf = append(buf, []byte(",b=")...) | |
buf = strconv.AppendInt(buf, v2, 10) | |
buf = append(buf, []byte(",c=")...) | |
buf = strconv.AppendInt(buf, v3, 10) | |
buf = append(buf, []byte("\n")...) | |
buf = buf[0:] | |
} | |
} | |
// A friendlier API, and faster than Fmt.Sprintf or Fmt.Fprintf, but | |
// slowed down by all the method calls. | |
func BenchmarkLineBuilder(b *testing.B) { | |
bldr := NewLineBuilder("foo ") | |
for i := 0; i < b.N; i++ { | |
bldr.Append("a", v1) | |
bldr.Append("b", v2) | |
bldr.Append("c", v3) | |
var _ = bldr.Done() | |
} | |
} | |
// Pre-computing as much as possible up front and using just one | |
// method call per output is fastest of all. This is ~77% faster than | |
// using fmt.Sprintf. | |
func BenchmarkIntLineFormatter(b *testing.B) { | |
f := NewIntLineFormatter("foo ", "a", "b", "c") | |
for i := 0; i < b.N; i++ { | |
var _ = f.Format(v1, v2, v3) | |
} | |
} | |
func NewLineBuilder(prefix string) *LineBuilder { | |
b := &LineBuilder{ | |
buf: make([]byte, 0, 128), | |
prefixLen: len(prefix), | |
} | |
b.buf = append(b.buf, []byte(prefix)...) | |
return b | |
} | |
type LineBuilder struct { | |
buf []byte | |
prefixLen int | |
needComma bool | |
} | |
func (b *LineBuilder) Append(label string, n int64) { | |
if b.needComma { | |
b.buf = append(b.buf, []byte(","+label+"=")...) | |
} else { | |
b.buf = append(b.buf, []byte(label+"=")...) | |
b.needComma = true | |
} | |
b.buf = strconv.AppendInt(b.buf, n, 10) | |
} | |
func (b *LineBuilder) Done() []byte { | |
out := append(b.buf, byte('\n')) | |
// Reset | |
b.buf = b.buf[:b.prefixLen] | |
b.needComma = false | |
return out | |
} | |
func NewIntLineFormatter(prefix string, labels ...string) *IntLineFormatter { | |
f := &IntLineFormatter{ | |
buf: make([]byte, 0, 128), | |
prefixLen: len(prefix), | |
labels: make([][]byte, len(labels)), | |
} | |
// Precompute label sections | |
for i, label := range labels { | |
if i > 0 { | |
f.labels[i] = []byte("," + label + "=") | |
} else { | |
f.labels[i] = []byte(label + "=") | |
} | |
} | |
// Set prefix | |
f.buf = append(f.buf, []byte(prefix)...) | |
return f | |
} | |
type IntLineFormatter struct { | |
buf []byte | |
prefixLen int | |
labels [][]byte | |
} | |
func (f *IntLineFormatter) Format(ns ...int64) []byte { | |
for i, n := range ns { | |
f.buf = append(f.buf, f.labels[i]...) | |
f.buf = strconv.AppendInt(f.buf, n, 10) | |
} | |
out := append(f.buf, byte('\n')) | |
// Reset | |
f.buf = f.buf[:f.prefixLen] | |
return out | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Typical result: