zoukankan      html  css  js  c++  java
  • Go-For Range 性能研究

          文章转载地址: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 的性能消耗高于赋值消耗,这就是为什么优化没有起到作用 

  • 相关阅读:
    gitlab 本地 定时备份
    centos 7 部署 汉化版 gitlab
    ELK开机启动 service文件内容
    通过 kms 激活 office 2016
    让 kibana 后台启动的方案
    centos7 yum 安装 redis
    域账户登录时提示“你的账户配置不允许使用这台电脑。请试一下其他电脑” 解决方案
    gitlab 接入 openldap、AD
    VS访问不到TFS、VS连接TFS报TF30063
    php--纯静态和伪静态的区别与关系
  • 原文地址:https://www.cnblogs.com/leeyongbard/p/10394820.html
Copyright © 2011-2022 走看看