for和range性能大比拼!

能GET到的知识点

  • 什么场景使用for和range

1. 从一个遍历开始

万能的range遍历

  1. 遍历array/slice/strings

array

go 复制代码
package main  
  
import "fmt"  
  
func main() {  
    var UserIDList = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}  

    for i, v := range UserIDList {  
        fmt.Println(i, v)  
    }  
}

0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10

slice

go 复制代码
package main  
  
import "fmt"  
  
func main() {  
    var UserIDList = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}  
    var UerSlice = UserIDList[:]  

    for i, v := range UerSlice {  
        fmt.Println(i, v)  
    }  
}
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10

字符串

go 复制代码
func main(){
    var Username = "斑斑砖abc"  
    for i, v := range Username {  
        fmt.Println(i, v)  
    }
}


0 26001
3 26001
6 30742
9 97
10 98
11 99

range进行对array、slice类型遍历一切都正常,但是到了对字符串进行遍历时这里就出问题了,出问题主要在索引这一块。可以看出索引是每个字节的位置,在go语言中的字符串是UTF-8编码的字节序列。而不是单个的Unicode字符。遇到中文字符时需要使用多个字节表示,英文字符一个字节进行表示,索引0-3表示了一个字符及以此完后。

  1. 遍历map
go 复制代码
func ByMap() {  
    m := map[string]int{  
    "one": 1,  
    "two": 2,  
    "three": 3,  
    }  
    for k, v := range m {  
        delete(m, "two")  
        m["four"] = 4  
        fmt.Printf("%v: %v\n", k, v)  
    }  
}

one: 1
four: 4
three: 3
  • 和切片不同的是,迭代过程中,删除还未迭代到的键值对,则该键值对不会被迭代。
  • 在迭代过程中,如果创建新的键值对,那么新增键值对,可能被迭代,也可能不会被迭代。个人认为应该是hash的无序性问题
  • 针对 nil 字典,迭代次数为 0
  1. 遍历channel
go 复制代码
func ByChannel() {  
    ch := make(chan string)  
    go func() {  
        ch <- "a"  
        ch <- "b"  
        ch <- "c"  
        ch <- "d"  
        close(ch)  
    }()  
    time.Sleep(time.Second)
    for n := range ch {  
        fmt.Println(n)  
    }  
}
  • 针对于range对关闭channel的遍历,会直到把元素都读取完成。
  • 但是在for遍历会造成阻塞,因为for变量读取一个关闭的管道并不会进行退出,而是一直进行等待,但是如果关闭了会返回一个状态值可以根据该状态值判断是否需要操作

2.for和range之间奇怪的问题

2.1 无限遍历现象

for

css 复制代码
c := []int{1, 2, 3}  
for i := 0; i < len(c); i++ {  
    c = append(c, i)  
    fmt.Println(i)  
}

1
2
3
.
.
.
15096
15097
15098
15099
15100
15101
15102
15103
15104

range

go 复制代码
c := []int{1, 2, 3}  
for _, v := range c {  
    c = append(c, v)  
    fmt.Println(v)  
}

1
2
3

可以看出for循环一直在永无止境的进行追加元素。 range循环正常。原因:for循环的i < len(c)-1都会进行重新计算一次,造成了永远都不成立。range循环遍历在开始前只会计算一次,如果在循环进行修改也不会影响正常变量。

2.2 在for和range进行修改操作

for

css 复制代码
type UserInfo struct {  
    Name string  
    Age int  
}
var UserInfoList = [3]UserInfo{  
    {Name: "John", Age: 25},  
    {Name: "Jane", Age: 30},  
    {Name: "Mike", Age: 28},  
    }  
for i := 0; i < len(UserInfoList); i++ {  
 
    UserInfoList[i].Age += i  
}  
fmt.Println(UserInfoList)

0
1
2
[{John 25} {Jane 31} {Mike 30}]

range

css 复制代码
var UserInfoList = [3]UserInfo{  
    {Name: "John", Age: 25},  
    {Name: "Jane", Age: 30},  
    {Name: "Mike", Age: 28},  
    }  

  
for i, info := range UserInfoList {  
    info.Age += i  
}  
fmt.Println(UserInfoList)

[{John 25} {Jane 30} {Mike 28}]

可以看出for循环进行修改了成功,但是在range循环修改失效,为什么呢?因为range循环返回的是对该值的拷贝,所以修改失效。for循环修相当于进行原地修改了。但如果在for循环里面进行赋值修改操作,那么修改也会进行失效 具体如下

css 复制代码
var UserInfoList = [3]UserInfo{  
    {Name: "John", Age: 25},  
    {Name: "Jane", Age: 30},  
    {Name: "Mike", Age: 28},  
}  
for i := 0; i < len(UserInfoList); i++ {  
    fmt.Println(i)  
    item := UserInfoList[i]  
    item.Age += i  
  
}  
  

fmt.Println(UserInfoList)
> [{John 25} {Jane 30} {Mike 28}]

3. Benchmark大比拼

主要是针对大类型结构体

go 复制代码
type Item struct {  
    id int  
    val [4096]byte  
}

for_test.go

css 复制代码
func BenchmarkForStruct(b *testing.B) {  
    var items [1024]Item  
    for i := 0; i < b.N; i++ {  
        length := len(items)  
        var tmp int  
        for k := 0; k < length; k++ {  
            tmp = items[k].id  
        }  
        _ = tmp  
    }  
}
func BenchmarkRangeStruct(b *testing.B) {  
    var items [1024]Item  
    for i := 0; i < b.N; i++ {  
        var tmp int  
        for _, item := range items {  
            tmp = item.id  
        }  
        _ = tmp  
    }  
}
bash 复制代码
goos: windows
goarch: amd64
pkg: article/02fortest
cpu: AMD Ryzen 5 5600G with Radeon Graphics
BenchmarkForStruct-12            2503378               474.8 ns/op             0 B/op          0 allocs/op
BenchmarkRangeStruct-12             4983            232744 ns/op               0 B/op          0 allocs/op
PASS
ok      article/02fortest       3.268s

可以看出 for 的性能大约是 range600 倍。

为什么会产生这么大呢?

上述也说过,range遍历会对迭代的值创建一个拷贝。在占据占用较大的结构时每次都需要进行做一次拷贝,取申请大约4kb的内存,显然是大可不必的。所以在对于占据较大的结构时,应该使用for进行变量操作。

总结

如何选择合适的遍历,在针对与测试场景的情况下,图便捷可以使用range,毕竟for循环需要写一堆的条件,初始值等。但是如果遍历的元素是个占用大个内存的结构的话,避免使用range进行遍历。且如果需要进行修改操作的话只能用for遍历来修改,其实range也可以进行索引遍历的,在本文为写,读者可以去尝试一下。

参考

相关推荐
27669582921 天前
阿里1688 阿里滑块 231滑块 x5sec分析
java·python·go·验证码·1688·阿里滑块·231滑块
Moment2 天前
在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄
前端·后端·go
唐僧洗头爱飘柔95273 天前
(Go基础)变量与常量?字面量与变量的较量!
开发语言·后端·golang·go·go语言初上手
黑心萝卜三条杠3 天前
【Go语言】深入理解Go语言:并发、内存管理和垃圾回收
google·程序员·go
不喝水的鱼儿3 天前
【LuatOS】基于WebSocket的同步请求框架
网络·websocket·网络协议·go·luatos·lua5.4
微刻时光3 天前
程序员开发速查表
java·开发语言·python·docker·go·php·编程语言
lidenger4 天前
服务认证-来者何人
后端·go
幼儿园老大*4 天前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
童先生4 天前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*5 天前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go