Skip to content

Instantly share code, notes, and snippets.

@mjs
Last active April 3, 2018 02:03
Show Gist options
  • Save mjs/bbaf6db6b63f0e30eef1d10b0fc1fd00 to your computer and use it in GitHub Desktop.
Save mjs/bbaf6db6b63f0e30eef1d10b0fc1fd00 to your computer and use it in GitHub Desktop.
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
}
@mjs
Copy link
Author

mjs commented Apr 3, 2018

Typical result:

$ go test -bench=.
goos: linux
goarch: amd64
pkg: sandbox/byte-formatting
BenchmarkSprintf-4            	 5000000	       286 ns/op
BenchmarkBuffer-4             	 5000000	       246 ns/op
BenchmarkManualAppend-4       	10000000	       173 ns/op
BenchmarkLineBuilder-4        	10000000	       158 ns/op
BenchmarkIntLineFormatter-4   	20000000	        65.0 ns/op
PASS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment