benchmark测试(以字符串拼接为例)
1、首先实现拼接字符串拼接函数
// generate_test.go
package main
import (
"math/rand"
"testing"
"time"
)
// 首先实现一个生成长度为 n 的随机字符串的函数
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func randomString(n int) string {
b := make([]byte, n)
for i := b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
// 使用+实现字符串拼接 拼接n次
func plusContact(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s += str
}
return s
}
// 使用fmt.Sprintf实现
func sprintfContact(n int, str string) string {
s := "
for i := 0; i < n; i++ {
s = fmt.Sprintf("%s%s", s, str)
}
return s
}
// 使用strings.Builder(非线程安全,StringBuilder 相较于 StringBuffer 有速度优势)
func builderConcat(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
}
// 使用strings.Buffer
func bufferConcat(n int, s string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(s)
}
return buf.String()
}
// 使用bytes.Buffer
func bufferConcat(n int, s string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(s)
}
}
// 使用[]byte
func byteConcat(n int, str string) string {
for i := 0; i < n; i++ {
buf = append(buf, str...)
}
}
// 分配容量的[]byte
unc preByteConcat(n int, str string) string {
buf := make([]byte, 0, n*len(str))
for i := 0; i < n; i++ {
buf = append(buf, str...)
}
return string(buf)
}
// 直接在这写过benchmark测试文件吧
// 生成长度为10的字符串,拼接1w次
func benchmark(b *testing.B, f func(int, string) string) {
var str = randomString(10)
for i := 0; i < b.N; i++ {
f(10000, str)
}
}
func BenchmarkPlusConcat(b *testing.B) { benchmark(b, plusConcat) }
func BenchmarkSprintfConcat(b *testing.B) { benchmark(b, sprintfConcat) }
func BenchmarkBuilderConcat(b *testing.B) { benchmark(b, builderConcat) }
func BenchmarkBufferConcat(b *testing.B) { benchmark(b, bufferConcat) }
func BenchmarkByteConcat(b *testing.B) { benchmark(b, byteConcat) }
func BenchmarkPreByteConcat(b *testing.B) { benchmark(b, preByteConcat) }
测试该用例
$ go test -bench="Concat$" -benchmem .
goos: darwin
goarch: amd64
pkg: example
BenchmarkPlusConcat-8 19 56 ms/op 530 MB/op 10026 allocs/op
BenchmarkSprintfConcat-8 10 112 ms/op 835 MB/op 37435 allocs/op
BenchmarkBuilderConcat-8 8901 0.13 ms/op 0.5 MB/op 23 allocs/op
BenchmarkBufferConcat-8 8130 0.14 ms/op 0.4 MB/op 13 allocs/op
BenchmarkByteConcat-8 8984 0.12 ms/op 0.6 MB/op 24 allocs/op
BenchmarkPreByteConcat-8 17379 0.07 ms/op 0.2 MB/op 2 allocs/op
PASS
ok example 8.627s
参数说明:
1、属性 b.N 表示这个用例需要运行的次数
2、BenchmarkFib-8 中的 -8 即 GOMAXPROCS,默认等于 CPU 核数。可以通过 -cpu 参数改变 GOMAXPROCS,-cpu 支持传入一个列表作为参数,例如:
$ go test -bench='Fib$' -cpu=2,4 .
3、第二列参数指的是用例执行的次数 ,第三列指的是没次花费时间,第四列指的是分配多少内存,第五列指的是分配了几次内存
//------------cpu默认8核----执行次数--每次花费时间---内存分配--------分配了几次
BenchmarkPlusConcat-8 19 56 ms/op 530 MB/op 10026 allocs/op
由上面的结论可知
- 使用 + 和 fmt.Sprintf 的效率是最低的,和其余的方式相比,性能相差约 1000 倍,而且消耗了超过 1000 倍的内存。当然
fmt.Sprintf 通常是用来格式化字符串的,一般不会用来拼接字符串。 - strings.Builder、bytes.Buffer 和 []byte
的性能差距不大,而且消耗的内存也十分接近,性能最好且消耗内存最小的是
preByteConcat,这种方式预分配了内存,在字符串拼接的过程中,不需要进行字符串的拷贝,也不需要分配新的内存,因此性能最好,且内存消耗最小。