考虑这个基准测试,我们比较 map 访问和切换
var code = []int32{0, 10, 100, 100, 0, 10, 0, 10, 100, 14, 1000, 100, 1000, 0, 0, 10, 100, 1000, 10, 0, 1000, 12}
var mapCode = map[int32]int32{
0: 1,
10: 2,
100: 3,
1000: 4,
}
func BenchmarkMap(b *testing.B) {
success := int32(0)
fail := int32(0)
for n := 0; n < b.N; n++ {
// for each value in code array, do a specific action
for _, v := range code {
c, ok := mapCode[v]
if !ok {
fail++
} else {
success += c
}
}
}
}
func BenchmarkSwitch(b *testing.B) {
success := int32(0)
fail := int32(0)
for n := 0; n < b.N; n++ {
// for each value in code array, do a specific action
for _, v := range code {
switch v {
case 0:
success++
case 10:
success += 2
case 100:
success += 3
case 1000:
success += 4
default:
fail++
}
}
}
}
结果如下:
BenchmarkMap-2 5000000 277 ns/op 0 B/op 0 allocs/op
BenchmarkSwitch-2 30000000 48.2 ns/op 0 B/op 0 allocs/op
所以使用 map 似乎比 switch 慢得多。
我目前正在尝试使用类似于 BenchmarkMap()
的代码来优化函数,其中 map 访问
是瓶颈,但我不能使用开关,因为 map 是在程序启动时动态生成的(即它
可能会根据输入参数改变)
有没有办法通过动态生成的 map 获得与 switch x {}
类似的性能?
最佳答案
没有 map ,因为indexing map 在运行时进行评估,从 map 中获取元素涉及的操作不仅仅是单个( slice )索引。某些 switch
es(带有常量表达式的 case
分支)即使在编译时也可以/可能被优化。
但是 map 并不是唯一的“动态”结构。对于另一个,有 slices . slice 可以被索引,就像 map 一样。
是的, slice 是底层数组的连续段的描述符。这意味着如果您有一个像 1000
这样的索引,则 slice 至少需要有 1000+1 = 1001
个元素。
因此,如果您愿意为了性能而牺牲一些内存并使用 slice 而不是映射,您甚至可以使您的解决方案比使用 switch
的解决方案更快声明:
var sliceCode = []int32{
0: 1,
10: 2,
100: 3,
1000: 4,
}
func BenchmarkSlice(b *testing.B) {
success := int32(0)
fail := int32(0)
for n := 0; n < b.N; n++ {
// for each value in code array, do a specific action
for _, v := range code {
c := sliceCode[v]
if c == 0 {
fail++
} else {
success += c
}
}
}
}
基准测试结果:
BenchmarkMap-4 10000000 148 ns/op
BenchmarkSlice-4 100000000 17.6 ns/op
BenchmarkSwitch-4 50000000 31.0 ns/op
这个具体示例中的 slice 解决方案比 switch
解决方案快两倍!
注意事项:
我在上面提到过,如果你有一个像 1000
这样的索引,你至少需要 1001
个元素。这部分是正确的。例如,如果您有像 990..1000
这样的索引,您可以有一个像 index - 990
这样的简单索引转换逻辑,然后一个只有 11 个元素的 slice 将足够完美。
另请注意,在使用 comma-ok 惯用语索引 map 时,我们能够判断该元素是否在 map 中。对于 slice ,我们没有那个选项。所以我们必须从元素类型的有效集合中指定一个值,并将其用作“缺失”信号。在上面的示例中,0
对我们来说是完美的,因为它没有被使用(所有未明确列出的元素默认设置为 0
)。如果在您的示例中可以使用所有有效的 int32
值,则另一种选择是使用包装器或指针类型作为 slice 的元素类型,它可以具有 nil
值, 表示缺少索引元素。
关于performance - go 中的 map vs switch 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46789259/