我正在尝试优化结构 FmtC
的 String()
函数的速度。基于以下基准。
- 如果我使用
strings.Builder
预分配 1024 字节,它比fmt.Sprint
(437 ns)慢(624 ns)。 - 如果我预先分配 32 字节,
strings.Builder
会更快(74.8 ns),但如果FmtC
包含更多成员字段,它就没有用了。 - 如果我通过 slice 追加方法预分配 1024 字节,它是最慢的 (2337 ns)。
go版本:go1.12.7 linux/amd64。
去测试 -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
主.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()
}
(编辑:)将s := make([]byte, preAllocatedSize)
的错误改成s := make([]byte, 0, preAllocatedSize)
,结果是:在这个简单的测试中,只有预分配的 1024 字节版本比 fmt.Sprint
慢。
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
最佳答案
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() }
您的第一个错误是您的代码无法编译。
BenchmarkStringsBuilder_128 redeclared in this block
go test -v -bench=.
你的第二个错误是没有使用 go test
选项 -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
您可能会发现此函数比您的函数更快。
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)
}
关于go - 为什么 strings.Builder 在我的测试程序中追加字符串比 fmt.Sprint 慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57437958/