GOLANG
map[int]interface{}map[int]struct{}
nilstruct{}{}
package main
func main() {}
func MapWithInterface() {
m := map[int]interface{}{}
for i := 1; i <= 100; i++ {
m[i] = nil
}
}
func MapWithEmptyStruct() {
m := map[int]struct{}{}
for i := 1; i <= 100; i++ {
m[i] = struct{}{}
}
}
基准测试结果如下:
package main
import "testing"
func Benchmark_Interface(b *testing.B) {
for i := 0; i < b.N; i++ {
MapWithInterface()
}
}
func Benchmark_EmptyStruct(b *testing.B) {
for i := 0; i < b.N; i++ {
MapWithEmptyStruct()
}
}
这些基准测试的结果如下所示:
goos: darwin
goarch: amd64
pkg: awesomeProject1
Benchmark_Interface-8 130419 8949 ns/op 7824 B/op 7 allocs/op
Benchmark_EmptyStruct-8 165147 6964 ns/op 3070 B/op 17 allocs/op
PASS
ok awesomeProject1 3.122s
nilstruct{}{}
TL;DR
map[int]struct{}
详细解释
在回答这个问题之前,我们需要了解 map 的初始化和 map 结构。我们将先介绍这些主题,然后分析一些基准测试,以了解我们在这里尝试理解的两种 map 类型的性能。我创建了一个存储库,其中包含一些测试,以帮助理解本文的答案。因此,如果你在文本中看到对测试或基准测试的引用,它可能在存储库中。
Map 的初始化
map 有两种初始化方法:
make(map[int]string)make(map[int]string, hint)hint
hint
Map 的结构
在 Go 中,map 是一个哈希表,将其键值对存储到 bucket 中。每个 bucket 是一个数组,最多可容纳 8 个条目。默认的 bucket 数量为 1。当每个 bucket 中的条目数量达到平均负载(也称为负载因子)时,map 将通过将 bucket 数量加倍来变大。每次 map 增长时,都会为新条目分配内存。实际上,每当 bucket 的负载达到 6.5 或更高时,map 就会增长。这个值是硬编码的,并且被选择以优化内存使用。
hmapmap
正如你在上面的链接中所看到的,map 的内部结构很复杂。在下面的链接中,你可以找到一些关于如何“黑掉”map类型的见解。Aleksandr Kochetkov 做了一个非常好的工作,展示了一些 map 内部的细节。
需要注意的一件重要事情是,maps 会跟踪可以持有指针的键和值。如果 bucket 中的条目不能持有任何指针,则将标记该 bucket 为不包含指针,并且 map 将创建溢出 bucket(这意味着更多的内存分配)。这避免了 GC 的不必要开销。请参见这个结构体中的评论(第 132 行),以及这个帖子 作为参考。
基准测试分析
struct{}map[int]struct{}interface{}
ptrdatamap[int]struct{}map[int]interface{}
Benchmark_InterfaceBenchmark_EmptyStructBenchmark_Interface(*hmap)createOverflow
Benchmark_EmptyStruct CPU profile
Benchmark_EmptyStruct CPU profile (png, )
Benchmark_Interface CPU profile
Benchmark_Interface CPU profile (png, )
Benchmarks Results
hint
InterfaceEmptyStructInterfaceEmptyStruct
Conclusion
map[int]interface{}map[int]struct{}Test_EmptyStructValueSizestruct{}{}nilinterface{}interface{}TestNilInterfaceValueSizenilinterface{}map[int]struct{}