今天在读《Go语言学习笔记》5.4 字典章节的时候。看到有一个例子通过benchmark介绍 map的一个知识点--创建map的时候最好预估一个足够的大小,这样可以避免频繁的扩容导致申请内存和rehash的操作。
因为之前自己也没有写过benchmark,所以就想着动手操作一下。但是发现书上的代码并不完整。也没有运行命令。自己捣鼓了一下,也顺便分享给小伙伴们。
整个代码如下
代码语言:javascript复制package map_test
import "testing"
// 书上少了 上面这2行。可能是觉得大家都知道。
func test() map[int]int {
m := make(map[int]int)
for i := 0; i < 1000; i {
m[i] = i
}
return m
}
func testCap() map[int]int {
// 预先准备空间
m := make(map[int]int, 1000)
for i := 0; i < 1000; i {
m[i] = i
}
return m
}
func BenchmarkTest(b *testing.B) {
for i := 0; i < b.N; i {
test()
}
}
func BenchmarkTestCap(b *testing.B) {
for i := 0; i < b.N; i {
testCap()
}
}
这里一开始我的文件名:map_expansion.go
当我运行 benchmark 命令时,发现没有效果。
代码语言:javascript复制go test -bench=. -benchmem
因为这个benchmark也是一个测试,所以文件名要以_test结尾。
可以看到结果如下
代码语言:javascript复制goos: darwin
goarch: amd64
pkg: example.com/m/src/map/benchmark
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkTest-12 18309 66018 ns/op 86554 B/op 64 allocs/op
BenchmarkTestCap-12 43518 28886 ns/op 41097 B/op 6 allocs/op
PASS
ok example.com/m/src/map/benchmark 3.995s
刚开始看到这个结果其实比较懵,后面3个带单位的还可以猜一下。但是前面2个就完全不知道啥意思了。
第一列 就是测试方法名 -12 表示GOMAXPROCS(线程数)的值为12。
第二列 18309和43518表示执行了多少次。对应代码里的b.N的数量。
第三列 从单位(ns/op)也可以猜到每个操作耗时多少ns。
第四列 每个操作需要申请多少内存。
第五列 每个操作申请了几次内存。
从测试结果来看TestCap的效果 确实比Test的效果要好很多尤其是申请内存的次数上要少很多。不进行预分配的情况 每次操作要申请64次内存而预分配1000后每次操作,需要申请6次内存。
但是我们又知道 map的负载因子是 0.65 , 也就是说就算预分配了1000,也会触发扩容。于是我又做了一组实验用在Cap函数里预分配1650
代码语言:javascript复制func testCap() map[int]int {
// 预先准备空间
m := make(map[int]int, 1650)
for i := 0; i < 1000; i {
m[i] = i
}
return m
}
可以看到结果还是每次操作申请了6次内存。
直到加到预申请1665才会每次操作申请2次内存。但是每次操作申请的内存数量又上来了。
哪位大佬可以指点一二?