Golang 创建map时的一个骚操作,可以提升性能!

2022-12-02 20:56:01 浏览数 (1)

今天在读《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次内存。但是每次操作申请的内存数量又上来了。

哪位大佬可以指点一二?

0 人点赞