performance - go 中的 map vs switch 性能

标签 performance go hashmap switch-statement

考虑这个基准测试,我们比较 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 )索引。某些 switches(带有常量表达式的 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/

相关文章:

将矩阵中的 0 替换为 NA

arrays - 如何通过在 slice 上查找来从 slice 复制到数组

go - 设置 %s 从字节编码的十六进制到变量 golang

c++ - HashMap : What's the difference between a node map and a flat map?

java - 出现错误 : Exception in thread "main" while executing program for different loop conditions in static block

ruby - 如何在散列中汇总散列?

c - 如何强制两个进程在同一个CPU上运行?

python - 对面向队列的函数使用多处理后没有性能提升

javascript - CSS Sprite 性能

function - Go 中是否允许++?