主要观点总结
文章介绍了Go语言在1.24版本中Map的新特性及其带来的性能提升,同时也指出了新特性引发的问题点,包括map[int64]struct{}的槽空间占用问题以及可能对CPU的影响。文章还讨论了解决此问题的不同布局方式,包括键/值一起存储和所有键集中存储的对比,以及社区对此问题的反馈。最后,文章提到这个问题有望在Go1.25中得到解决或缓解,并给出了参考资料。
关键观点总结
关键观点1: Go语言在1.24版本中对Map进行了改进,使用了Swiss Table替换Hashmap的原始实现,带来了性能提升。
使用Swiss Table作为Map的底层数据结构,带来了显著的综合性能提高。
关键观点2: 在Go1.24新引入的Map特性中,发现了一个问题点:map[int64]struct{}的槽空间占用问题。
每个槽(slot)需要16字节空间,而不是预期的8字节。这是因为Map内部定义存储方式的副作用以及struct大小规则导致的。
关键观点3: 问题点引发了社区的关注,有开发者提出了解决方案,涉及更改布局以优化缓存未命中的问题。
对比了键/值一起存储和所有键集中存储两种布局方式的优缺点,并讨论了控制字的作用。
关键观点4: 社区反馈显示,在Go1.24中map[uint64]struct{}的性能比Go1.23低10%。
具体表现在Prometheus项目中,使用Go1.24时访问大Map会消耗更多的CPU。
关键观点5: 目前这个问题点有望在Go1.25中得到解决或缓解。
文章提到软件设计没有银弹,选择了一种方案就可能会有另一种方案的短板。
正文
是日常中用来占位表现最为常用的一个用法之一。
原因
造成这个现象的原因在于 Map
内部定义存储
[1]
方式的所造成的副作用:
type group struct {
ctrl uint64
slots [abi.SwissMapGroupSlots]struct {
key keyType
elem elemType
}
}
原因在于:
-
-
Go 编译器中的
struct
大小规则规定,如果
struct
以零大小(zero-size)类型结束,则该字段将获得 1 字节的空间。
-
会设计这个机制的目的是:防止有人创建指向最后一个字段的指针,Go 编译器不希望指针指向分配结束的位置。
-
划重点:最后一个字段
keyType
实际上使用了整整 8 字节,这是 KVKVKVKV(K/V 一起存储) 由于对齐要求而浪费空间的最极端情况。
这个问题出现在了新的 Map 新引入的
swissmaps
中。
解决思路
实际上还是 @Michael Pratt,既发现问题,还提出如何解决问题。简直是专业的大好人!其提出了新的 issues《
runtime: map cold cache improvements
[2]
》: