Reputation: 16042
I'm trying to optimize speed of a String()
function of struct FmtC
. Based on following benchmark.
strings.Builder
, it is slower (624 ns) than fmt.Sprint
(437 ns).strings.Builder
is faster (74.8 ns) but it is not useful if FmtC
contains more member fields.go version: go1.12.7 linux/amd64.
go test -v -bench=. -benchmem
BenchmarkFmtSprint_32-2 3000000 435 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_32-2 20000000 74.8 ns/op 32 B/op 1 allocs/op
BenchmarkSliceAppendString_32-2 10000000 213 ns/op 160 B/op 3 allocs/op
BenchmarkSliceAppendBytes_32-2 10000000 143 ns/op 96 B/op 2 allocs/op
BenchmarkFmtSprint_128-2 3000000 437 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_128-2 10000000 125 ns/op 128 B/op 1 allocs/op
BenchmarkSliceAppendString_128-2 3000000 478 ns/op 544 B/op 3 allocs/op
BenchmarkSliceAppendBytes_128-2 5000000 312 ns/op 384 B/op 2 allocs/op
BenchmarkFmtSprint_1024-2 3000000 437 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_1024-2 2000000 624 ns/op 1024 B/op 1 allocs/op
BenchmarkSliceAppendString_1024-2 500000 2337 ns/op 3456 B/op 3 allocs/op
BenchmarkSliceAppendBytes_1024-2 5000000 310 ns/op 384 B/op 2 allocs/op
main.go
package main
import (
"fmt"
"strconv"
"strings"
)
type FmtC struct {
Field1 uint32
Field2 [5]byte
}
var preAllocatedSize = 1024
func (c FmtC) FmtSprint() string {
return fmt.Sprint("{Field1:", c.Field1, " Field2:",
string(c.Field2[:]), "}")
}
func (c FmtC) StringsBuilder() string {
var s strings.Builder
s.Grow(preAllocatedSize) // output length width less than 1024 bytes
s.WriteString("{Field1:")
s.WriteString(strconv.FormatUint(uint64(c.Field1), 10))
s.WriteString(" Field2:")
s.Write(c.Field2[:])
s.WriteString("}")
return s.String()
}
func (c FmtC) SliceAppendString() string {
s := make([]byte, preAllocatedSize)
s = append(s, "{Field1:"...)
s = strconv.AppendUint(s, uint64(c.Field1), 10)
s = append(s, " Field2:"...)
s = append(s, c.Field2[:]...)
s = append(s, "}"...)
return string(s)
}
func (c FmtC) SliceAppendBytes() []byte {
s := make([]byte, preAllocatedSize)
s = append(s, "{Field1:"...)
s = strconv.AppendUint(s, uint64(c.Field1), 10)
s = append(s, " Field2:"...)
s = append(s, c.Field2[:]...)
s = append(s, "}"...)
return s
}
func main() {
}
main_test.go
package main
import (
"testing"
)
var c = FmtC{5, [5]byte{'h', 'e', 'l', 'l', 'o'}}
func BenchmarkFmtSprint_32(b *testing.B) {
preAllocatedSize = 32
for n := 0; n < b.N; n++ {
c.FmtSprint()
}
b.StopTimer()
}
func BenchmarkStringsBuilder_32(b *testing.B) {
preAllocatedSize = 32
for n := 0; n < b.N; n++ {
c.StringsBuilder()
}
b.StopTimer()
}
func BenchmarkSliceAppendString_32(b *testing.B) {
preAllocatedSize = 32
for n := 0; n < b.N; n++ {
c.SliceAppendString()
}
b.StopTimer()
}
func BenchmarkSliceAppendBytes_32(b *testing.B) {
preAllocatedSize = 32
for n := 0; n < b.N; n++ {
c.SliceAppendBytes()
}
b.StopTimer()
}
func BenchmarkFmtSprint_128(b *testing.B) {
preAllocatedSize = 128
for n := 0; n < b.N; n++ {
c.FmtSprint()
}
b.StopTimer()
}
func BenchmarkStringsBuilder_128(b *testing.B) {
preAllocatedSize = 128
for n := 0; n < b.N; n++ {
c.StringsBuilder()
}
b.StopTimer()
}
func BenchmarkSliceAppendString_128(b *testing.B) {
preAllocatedSize = 128
for n := 0; n < b.N; n++ {
c.SliceAppendString()
}
b.StopTimer()
}
func BenchmarkSliceAppendBytes_128(b *testing.B) {
preAllocatedSize = 128
for n := 0; n < b.N; n++ {
c.SliceAppendBytes()
}
b.StopTimer()
}
func BenchmarkFmtSprint_1024(b *testing.B) {
preAllocatedSize = 1024
for n := 0; n < b.N; n++ {
c.FmtSprint()
}
b.StopTimer()
}
func BenchmarkStringsBuilder_1024(b *testing.B) {
preAllocatedSize = 1024
for n := 0; n < b.N; n++ {
c.StringsBuilder()
}
b.StopTimer()
}
func BenchmarkSliceAppendString_1024(b *testing.B) {
preAllocatedSize = 1024
for n := 0; n < b.N; n++ {
c.SliceAppendString()
}
b.StopTimer()
}
func BenchmarkSliceAppendBytes_1024(b *testing.B) {
preAllocatedSize = 128
for n := 0; n < b.N; n++ {
c.SliceAppendBytes()
}
b.StopTimer()
}
(Edit:) After changing the mistake of s := make([]byte, preAllocatedSize)
to s := make([]byte, 0, preAllocatedSize)
, the result is: only pre-allocated 1024 bytes version is slower than fmt.Sprint
in this simple test.
BenchmarkFmtSprint_32-2 3000000 432 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_32-2 20000000 75.2 ns/op 32 B/op 1 allocs/op
BenchmarkSliceAppendString_32-2 20000000 112 ns/op 64 B/op 2 allocs/op
BenchmarkSliceAppendBytes_32-2 20000000 64.2 ns/op 32 B/op 1 allocs/op
BenchmarkFmtSprint_128-2 3000000 437 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_128-2 10000000 123 ns/op 128 B/op 1 allocs/op
BenchmarkSliceAppendString_128-2 10000000 162 ns/op 160 B/op 2 allocs/op
BenchmarkSliceAppendBytes_128-2 20000000 110 ns/op 128 B/op 1 allocs/op
BenchmarkFmtSprint_1024-2 3000000 429 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_1024-2 2000000 626 ns/op 1024 B/op 1 allocs/op
BenchmarkSliceAppendString_1024-2 2000000 653 ns/op 1056 B/op 2 allocs/op
BenchmarkSliceAppendBytes_1024-2 10000000 110 ns/op 128 B/op 1 allocs/op
Upvotes: 1
Views: 402
Reputation: 166885
main_test.go
:func BenchmarkStringsBuilder_128(b *testing.B) { preAllocatedSize = 128 for n := 0; n < b.N; n++ { c.StringsBuilder() } b.StopTimer() } func BenchmarkStringsBuilder_128(b *testing.B) { preAllocatedSize = 128 for n := 0; n < b.N; n++ { c.StringsBuilder() } b.StopTimer() }
Your first error is that your code doesn't compile.
BenchmarkStringsBuilder_128 redeclared in this block
go test -v -bench=.
Your second error is not using go test
option -benchmem
.
$ go version
go version devel +9c1f14f376 Fri Aug 9 20:26:42 2019 +0000 linux/amd64
$ go test -bench=. -benchmem
BenchmarkFmtSprint_32-8 5116165 207 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_32-8 34339864 37.1 ns/op 32 B/op 1 allocs/op
BenchmarkSliceAppendString_32-8 12525960 85.5 ns/op 160 B/op 3 allocs/op
BenchmarkSliceAppendBytes_32-8 17084019 62.0 ns/op 96 B/op 2 allocs/op
BenchmarkFmtSprint_128-8 5681800 205 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_128-8 26086238 46.3 ns/op 128 B/op 1 allocs/op
BenchmarkSliceAppendString_128-8 9424910 126 ns/op 544 B/op 3 allocs/op
BenchmarkSliceAppendBytes_128-8 13260948 88.7 ns/op 384 B/op 2 allocs/op
BenchmarkFmtSprint_1024-8 5536604 205 ns/op 64 B/op 4 allocs/op
BenchmarkStringsBuilder_1024-8 8897110 133 ns/op 1024 B/op 1 allocs/op
BenchmarkSliceAppendString_1024-8 2764279 433 ns/op 3456 B/op 3 allocs/op
BenchmarkSliceAppendBytes_1024-8 13479661 88.6 ns/op 384 B/op 2 allocs/op
You may find this function is faster than your functions.
import (
"strconv"
)
type FmtC struct {
Field1 uint32
Field2 [5]byte
}
func (c FmtC) String() string {
s := make([]byte, 0, 32)
s = append(s, "{Field1:"...)
s = strconv.AppendUint(s, uint64(c.Field1), 10)
s = append(s, " Field2:"...)
s = append(s, c.Field2[:]...)
s = append(s, "}"...)
return string(s)
}
Upvotes: 2