• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

for和range性能大比拼

武飞扬头像
搬运工李
帮助4

能GET到的知识点

  • 什么场景使用for和range

1. 从一个遍历开始

万能的range遍历

  1. 遍历array/slice/strings

array

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

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

字符串

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
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
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

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

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

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

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循环里面进行赋值修改操作,那么修改也会进行失效 具体如下

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大比拼

主要是针对大类型结构体

type Item struct {  
    id int  
    val [4096]byte  
}

for_test.go

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  
    }  
}

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进行变量操作。

总结

参考

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfgafcb
系列文章
更多 icon
同类精品
更多 icon
继续加载