GolangNote

Golang笔记

Golang Range 的性能提升Tip

Permalink

Go 语言里使用 range 可以方便遍历数组(array)、切片(slice)、字典(map)和信道(chan)。这里主要关注他们的性能。

Range 的性能提升Tip

一般情况下面这三种方式遍历,性能没多大区别

Go: for range
1
2
3
for k := 0; k < length; k++
for k := range items
for k, v := range items

但是,如果列表是 struct,而且要读取 struct 里面的内容,就要注意了,毕竟性能相差近 900 倍。

测试代码

Go: range struct
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
	}
}

输出结果:

plaintext: 输出结果
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 迭代时,返回的是拷贝

Go: range copy
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 ,则 forrange 的性能都差不多。

结论

如果 items 的内容是 struct 不是指针,尽量避免这种写法

Go: range 性能差,不推荐
1
2
3
for _, item := range items {
	tmp = item.id
}

而要这么写

Go: rangeIndex 性能好,但写起来有点麻烦
1
2
3
for k := range items {
	tmp = items[k].id
}

参考

本文网址: https://golangnote.com/topic/308.html 转摘请注明来源

Related articles

Write a Comment to "Golang Range 的性能提升Tip"

Submit Comment Login
Based on Golang + fastHTTP + sdb | go1.18 Processed in 2ms