package main
import (
"maps"
"slices"
"testing"
)
//使用三种不同的方式实现把map的key元素转换成slice
// BenchmarkSdkIter 使用go1.23新的迭代器实现
func BenchmarkSdkIter(b *testing.B) {
mp := make(map[int]int)
for i := range 50000 {
mp[i] = i
}
b.ResetTimer()
for range b.N {
_ = slices.Collect(maps.Keys(mp))
}
}
func Keys[K comparable, V any](in map[K]V) []K {
result := make([]K, 0, len(in))
for k := range in {
result = append(result, k)
}
return result
}
// BenchmarkGeneric 使用泛型
func BenchmarkGeneric(b *testing.B) {
mp := make(map[int]int)
for i := range 50000 {
mp[i] = i
}
b.ResetTimer()
for range b.N {
_ = Keys(mp)
}
}
func keys(maps map[int]int) []int {
keySlice := make([]int, 0, len(maps))
for k := range maps {
keySlice = append(keySlice, k)
}
return keySlice
}
// BenchmarkRawForLoop使用最原始的办法
func BenchmarkRawForLoop(b *testing.B) {
mp := make(map[int]int)
for i := range 50000 {
mp[i] = i
}
b.ResetTimer()
for range b.N {
_ = keys(mp)
}
}
测试结果
结果即在意料之中又在意料之外
意料之中的是 for loop 稍快于 泛型 快于 go1.23新的迭代器
意料之外的是go1.23的迭代器居然慢了这么多,速度只有for loop 的一半
很乐意看到go团队终于实现了go迭代器的统一,但是这个效果真的不尽人意。仔细分析原因其实也很清楚,go1.23的迭代器中有大量的回调函数,
简单理解:
1.使用原生的for loop相当于只有1个 50000的循环
2.使用泛型也是只有1个50000的循环,但是每次迭代比for loop 多了一次解引用
3.1.23的迭代器就不一样了,除了1次50000的循环,每次迭代都是一次函数回调的过程,而且slices.Collect还不能预申请内存,自然要慢非常多了
参考
1.https://www.gingerbill.org/article/2024/06/17/go-iterator-design/