for和range性能大比拼!

能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
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
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
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
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
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
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
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
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)
}
}
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)  
    }  
}
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
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
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
c := []int{1, 2, 3}  
for _, v := range c {  
    c = append(c, v)  
    fmt.Println(v)  
}




1

2

3
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}]
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}]
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}]
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}]
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}]
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}]
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
}
type Item struct {  
    id int  
    val [4096]byte  
}
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
}
}
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  
    }  
}
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
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
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也可以进行索引遍历的,在本文为写,读者可以去尝试一下。

参考

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYC2soXj' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片