Go 语言里使用 range 可以方便遍历数组(array)、切片(slice)、字典(map)和信道(chan)。这里主要关注他们的性能。
一般情况下面这三种方式遍历,性能没多大区别
1
2
3
for k := 0; k < length; k++
for k := range items
for k, v := range items
但是,如果列表是 struct
,而且要读取 struct
里面的内容,就要注意了,毕竟性能相差近 900 倍。
测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package main
// go test -v range_test.go -bench "Benchmark" -benchmem
import (
"testing"
)
type Item struct {
id int
val [4096]byte
}
func BenchmarkForInt(b *testing.B) {
var items [1024]int
for i := 0; i < b.N; i++ {
length := len(items)
var tmp int
for k := 0; k < length; k++ {
tmp = items[k]
}
_ = tmp
}
}
func BenchmarkRangeIndexInt(b *testing.B) {
var items [1024]int
for i := 0; i < b.N; i++ {
var tmp int
for k := range items {
tmp = items[k]
}
_ = tmp
}
}
func BenchmarkRangeInt(b *testing.B) {
var items [1024]int
for i := 0; i < b.N; i++ {
var tmp int
for _, item := range items {
tmp = item
}
_ = 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 BenchmarkRangeIndexStruct(b *testing.B) {
var items [1024]Item
for i := 0; i < b.N; i++ {
var tmp int
for k := range items {
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
}
}
输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
% go test -v range_test.go -bench "Benchmark" -benchmem
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
BenchmarkForInt
BenchmarkForInt-8 3832728 301.0 ns/op 0 B/op 0 allocs/op
BenchmarkRangeIndexInt
BenchmarkRangeIndexInt-8 3808239 320.1 ns/op 0 B/op 0 allocs/op
BenchmarkRangeInt
BenchmarkRangeInt-8 3779557 300.4 ns/op 0 B/op 0 allocs/op
BenchmarkForStruct
BenchmarkForStruct-8 3988978 303.7 ns/op 0 B/op 0 allocs/op
BenchmarkRangeIndexStruct
BenchmarkRangeIndexStruct-8 3924799 308.1 ns/op 0 B/op 0 allocs/op
BenchmarkRangeStruct
BenchmarkRangeStruct-8 4389 261074 ns/op 0 B/op 0 allocs/op
原因
range 迭代时,返回的是拷贝,如果迭代的值内存占用很小的情况下,for 和 range 的性能几乎没有差异,但是如果每个迭代值内存占用很大,性能差就很明显。
下面的例子可以说明
range 迭代时,返回的是拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"
func main() {
persons := []struct{ no int }{{no: 1}, {no: 2}, {no: 3}}
for _, s := range persons {
s.no += 10
}
fmt.Println(persons) // [{1} {2} {3}]
for i := 0; i < len(persons); i++ {
persons[i].no += 100
}
fmt.Println(persons) // [{101} {102} {103}]
}
但如果 items
的内容是指针 []&item
,则 for
与 range
的性能都差不多。
结论
如果 items
的内容是 struct
不是指针,尽量避免这种写法
1
2
3
for _, item := range items {
tmp = item.id
}
而要这么写
1
2
3
for k := range items {
tmp = items[k].id
}
参考
本文网址: https://golangnote.com/topic/308.html 转摘请注明来源