在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
文章转载地址:https://www.flysnow.org/2018/10/20/golang-for-range-slice-map.html 如果我们要遍历某个数组,Map 集合、Slice 切片等,Go 语言(Golang) 为我们提供了比较好的 For Range 方式。 range 是一个关键字, 表示范围,和 for 配合使用可以迭代 数组、Map、Slice等集合,用法比较简洁,那么,这种 迭代方式和 for i=0;i<N;i++ 对比,性能怎么样呢?下面通过 Go 的基准测试对比一下两者的性能 For-Range 的基本使用 for range 的使用非常简单,这里演示两种集合类型的使用 package main import "fmt" func main() { ages := []string{"10","20","30"} for i,age := range ages { fmt.Println(i,age) } } 这里是针对 Slice 切片的迭代使用,使用 range 关键字返回两个变量 i,age ,第一个是 Slice 切片的索引,第二个 是 Slice 切片的内容,打印结果如下: 0 10 1 20 2 30 下面再看看 Map 的 for range 使用示例: package main import "fmt" func main() { ages:=map[string]int{"张三":15,"李四":20,"王武":36} for name,age:=range ages{ fmt.Println(name,age) } } 在使用for range迭代map的时候,返回的第一个变量是key,第二个变量是value,也就是我们例子中对应的name和ages 。我们运行程序看看输出结果: 张三 15 李四 20 王五 36 常规 For 循环对比 比如对于 Slice 切片,我们有两种迭代方式:一种是常规的for i:=0;i<N;i++的方式;一种是for range的方式,如下示例: package main_test import "testing" const N = 1000 // 常规 for 迭代 slice func ForSlice(s []string) { len := len(s) for i := 0; i < len; i++ { _, _ = i,s[i] } } // for range 迭代 slice func RangeForSlice(s []string) { for i, v := range s { _, _ = i, v } } // 初始化 slice func initSlice() []string{ s := make([]string,N) for i := 0;i < N;i++ { s[i] = "www.flysnow.org" } return s } // 基准测试函数 func BenchmarkForSlice(b *testing.B) { s := initSlice() b.ResetTimer() for i := 0;i < b.N;i++ { ForSlice(s) } } func BenchmarkRangeForSlice(b *testing.B) { s := initSlice() b.ResetTimer() for i := 0;i < b.N;i++ { RangeForSlice(s) } } 输出结果如下: goos: windows goarch: amd64 BenchmarkForSlice-8 5000000 303 ns/op BenchmarkRangeForSlice-8 3000000 512 ns/op PASS ok _/E_/GoProject/development/src 4.692s 从上面的输出结果可以看到,常规的 For 循环的性能更高。主要是因为 for range 是每次对循环元素的拷贝,而 for 循环,它获取集合内元素是通过 s[i],这种索引指针引用的方式,要比拷贝性能高得多 那么既然是元素拷贝的问题,我们在使用 range 方式迭代 slice 时候的目的也是为了获取元素,现在换一种方式实现 for range: // for range 迭代 slice func RangeForSlice(s []string) { for i, _ := range s { _, _ = i, s[i] } } 输出结果: goos: windows goarch: amd64 BenchmarkForSlice-8 5000000 303 ns/op BenchmarkRangeForSlice-8 5000000 308 ns/op PASS ok _/E_/GoProject/development/src 4.218s 结果和常规的 for 循环一样。原因是我们通过 _ 舍弃了元素的复制,然后通过 s[i] 方式获取迭代的元素 Map 遍历 对于 map 来说,我们并不能使用 for i=0;i<N;i++ 的方式,大部分我们使用 for range 的方式: package main_test import ( "fmt" "testing" ) const N = 1000 // for range For map func RangeForMap1(m map[int]string) { for k, v := range m{ _,_ = k,v } } // 初始化 map func initMap() map[int]string { m := make(map[int]string,N) for i := 0;i < N;i++ { m[i] = fmt.Sprint("www.flysnow.org",i) } return m } func BenchmarkRangeForMap1(b *testing.B) { m := initMap() b.ResetTimer() for i := 0; i < b.N; i++ { RangeForMap1(m) } } 运行结果如下: goos: windows goarch: amd64 BenchmarkRangeForMap1-8 100000 14535 ns/op PASS ok _/E_/GoProject/development/src 2.333s 相比较 slice,Map 遍历的性能更差。现在,我们使用上面优化遍历 slice 的方式优化遍历 map,减少值拷贝,如下示例: func RangeForMap2(m map[int]string) { for k, _ := range m{ _,_ = k,m[k] } } func BenchmarkRangeForMap2(b *testing.B) { m := initMap() b.ResetTimer() for i := 0; i < b.N; i++ { RangeForMap2(m) } } 运行结果如下: goos: windows goarch: amd64 BenchmarkRangeForMap1-8 100000 14290 ns/op BenchmarkRangeForMap2-8 100000 22240 ns/op PASS ok _/E_/GoProject/development/src 4.929s 我们看到,优化后的结果性能明显下降了,这和我们上面测试 slice 不一样,这次没有提升反而下降了 For Range 原理 range for slice: // The loop we generate: // len_temp := len(range) // range_temp := range // for index_temp = 0; index_temp < len_temp; index_temp++ { // value_temp = range_temp[index_temp] // index = index_temp // value = value_temp // original body // } 遍历 slice 前先是对要遍历的 slice 做一个拷贝,然后获取 slice 的长度作为循环次数,循环体中每次循环 会先获取元素值,我们还可以看到遍历过程中每次迭代都会对 index 和 value 进行赋值,如果数据量比较大或 者 value 为 string 时,对 value 的赋值操作可能是多余的,所以在上面我们使用 range 遍历 slice 的时候,可以 忽略 value,使用 slice[index] 的方式提升性能 range for map: // The loop we generate: // var hiter map_iteration_struct // for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) { // index_temp = *hiter.key // value_temp = *hiter.val // index = index_temp // value = value_temp // original body // } 看上面的实现方式,结合我们使用 for range slice 的 _ 优化方式,我们可以看到看似减少了一次赋值操作,但 是通过 key 查找 value 的性能消耗高于赋值消耗,这就是为什么优化没有起到作用 |
请发表评论