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 的性能消耗高于赋值消耗,这就是为什么优化没有起到作用
Go-For Range 性能研究的更多相关文章
- Linux性能研究(总)
http://www.vpsee.com/2009/11/linux-system-performance-monitoring-introduction/ http://www.jb51.net/L ...
- RazorEngine性能研究(反射的延深)
先说下结论 1)RazorEngine 确实很慢,编译过程特别慢,编译过后仍不适合大量重复调用的情况(一次调用可以接受). 2 ) RazorEngine 和 asp.net mvc 里的Razo ...
- DataReader类型化数据读取与装箱性能研究
前言 在各种ORM框架或者SQL映射框架(例如MyBatis,SOD框架之SQL-MAP功能)中,都有将查询的结果映射为内存对象的需求,包括映射到实体类.简单类型(例如Java的POJO,.NET的P ...
- HBase学习笔记-HBase性能研究(1)
使用Java API与HBase集群交互时,需要构建HTable对象,使用该对象提供的方法来进行插入/删除/查询等操作.要创建HTable对象,首先要创建一个带有HBase集群信息的配置对象Confi ...
- MYSQL开发性能研究——INSERT,REPLACE,INSERT-UPDATE性能比较
一.为什么要有这个实验 我们的系统是批处理系统,类似于管道的架构.而各个数据表就是管道的两端,而我们的程序就类似于管道本身.我们所需要做的事情无非就是从A表抽取数据,经过一定过滤.汇总等操作放置到B表 ...
- MYSQL开发性能研究——批量插入的优化措施
一.我们遇到了什么问题 在标准SQL里面,我们通常会写下如下的SQL insert语句. INSERT INTO TBL_TEST (id) VALUES(1); 很显然,在MYSQL中,这样的方 ...
- 《形式化分析工具Scyther性能研究》------摘抄整理
本篇论文的主要创新点在--------使用 Scyther工具发现对部分 KCI攻击搜索出现漏报的现象,并给出了存在的原因, 介绍了 形式化分析工具 AVispa全称是 Automated V ...
- Python中的range和xrange区别
range 函数说明:range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列. range示例: >>> r ...
- 【Python】range和xrange区别
转自:http://www.cnblogs.com/zhangjing0502/archive/2012/05/16/2503880.html range 函数说明:range([start,] ...
随机推荐
- tarjan算法(强连通分量 + 强连通分量缩点 + 桥(割边) + 割点 + LCA)
这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量, ...
- SharePoint 2010 查看dll的PublicKeyToken值方法
在做asp.net开发过程中,偶尔对有些dll,进行强制签名,那么在注册dll到gac的时候,就需要知道dll的PublicKeyToken值,如何通过简单的方法,来获得这个值呢,下面是一个很好又实用 ...
- SQL使用之关联更新、批量插入
使用场景 某个字段数据异常,利用另外一张表同步修改该表异常字段的数据; 关联更新 UPDATE tableName1 AS t1 LEFT JOIN tableName12 AS t2 ON t1.x ...
- 关于映射路径@ReuqestMapping的总结
何谓映射路径呢? 映射路径,就是匹配请求路径和执行方法关系的路径 基于注解的映射路径可以忽略前后缀,如: @RequestMapping(value="/say.do") @Req ...
- Mysql8安装与配置
网上的教程有很多,基本上大同小异.但是安装软件有时就可能因为一个细节安装失败.我也是综合了很多个教程才安装好的,所以本教程可能也不是普遍适合的. 安装环境:win7 1.下载zip安装包: MySQL ...
- 接口自动化框架(java)--2.接口用例POST请求,参数配置
这套框架的报告是自己封装的 Post类型的接口通常有请求参数,请求参数也是json类型,所以需要写一个类将请求参数序列化成json对象 以常见的登录接口为例 新建一个package,和postPara ...
- sping_依赖注入的三种方式
1. set注入:通过setxxx()给属性赋值 <!--id是对象--> <!--class是类--> <bean id = "student" ...
- if __name__ == '__main__' 这段代码怎么理解???
__name__是内置变量,可用于表示当前模块的名字,而“__main__”等于当前执行文件的名称. 两个名称搞不清没关系,往下看待会解释 对很多编程语言来说,程序都需要一个入口,例如C系列.Java ...
- Click One客户端安装后将安装目录删除,再从服务器下载安装无法安装解决办法
报错信息: 平台版本信息 Windows : 6.1.7601.65536 (Win32NT) Common Language Runtime : 4.0.30319.42000 System.Dep ...
- java学习之方法内部类
*方法内部类 * 在类中的方法内定义一个类 * 注意: * 1 方法内部类只能在类的方法内实例化不可以在此方法外实例化 * 2 .方法内部类对象只能用所在方法体中的变量不能改,jdk1.8可以 * 以 ...