简单整两句

在Go语言函数参数传递时,如果遇到数组传递或者结构体的传递,应尽量使用引用类型(指针),这样就可以避免内存复制,也就用不着GC对新复制的内存进行回收了。

翠花,上代码

package gc_friendly

import (
	"testing"
)

const NumOfElems = 1000

type Content struct {
	Detail [10000]int
}

func withValue(arr [NumOfElems]Content) int {
	//	fmt.Println(&arr[2])
	return 0
}

func withReference(arr *[NumOfElems]Content) int {
	//b := *arr
	//	fmt.Println(&arr[2])
	return 0
}

func TestFn(t *testing.T) {
	var arr [NumOfElems]Content
	//fmt.Println(&arr[2])
	withValue(arr)
	withReference(&arr)
}

func BenchmarkPassingArrayWithValue(b *testing.B) {
	var arr [NumOfElems]Content

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		withValue(arr)
	}
	b.StopTimer()
}

func BenchmarkPassingArrayWithRef(b *testing.B) {
	var arr [NumOfElems]Content

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		withReference(&arr)
	}
	b.StopTimer()
}

withValue函数中,使用了值传递方式传递参数,即不管入参内存有多大,都会再复制一份,将复制的内存传递给withValue函数。
withReference函数使用的是引用传递,即仅将入参的内存地址传递给函数,不会对整块内存进行复制。

Benchmark性能分析

使用-bench参数运行这段测试代码,查看两种方式的性能差别:

$ go test -v -bench=.
=== RUN   TestFn
--- PASS: TestFn (0.08s)
goos: windows
goarch: amd64
pkg: gc_friendly
BenchmarkPassingArrayWithValue-4  50  21839360 ns/op
BenchmarkPassingArrayWithRef-4  1000000000  0.332 ns/op
PASS
ok      gc_friendly     2.590s

可以看到,两种不同的参数传递方式的性能差距非常大。
使用了引用类型进行参数传递的方法仅用了0.332 ns/op。

打印GC日志分析性能

除了使用上面Benchmark分析性能方法,我们还可以通过打印GC日志来对比不同参数传递方式的性能差异。

BenchmarkPassingArrayWithValue函数分析:

$ GODEBUG=gctrace=1 go test -bench=BenchmarkPassingArrayWithValue
scvg: 0 MB released
scvg: inuse: 3, idle: 4, sys: 7, released: 3, consumed: 4 (MB)
scvg: 0 MB released
scvg: inuse: 2, idle: 5, sys: 7, released: 3, consumed: 4 (MB)
scvg: inuse: 2, idle: 5, sys: 7, released: 3, consumed: 4 (MB)
scvg: 0 MB released
......
gc 123 @3.118s 0%: 0+14+0 ms clock, 0+0/14/0+0 ms cpu, 152->152->0 MB, 153 MB goal, 4 P
scvg: inuse: 153, idle: 250, sys: 403, released: 186, consumed: 217 (MB)
      48          26047596 ns/op
PASS
ok      gc_friendly     3.481s

通过GC日志末尾的输出可以看到,函数执行了48次,GC执行了123次。

BenchmarkPassingArrayWithRef函数分析:

$ GODEBUG=gctrace=1 go test -bench=BenchmarkPassingArrayWithRef

scvg: 0 MB released
scvg: inuse: 0, idle: 163, sys: 163, released: 13, consumed: 150 (MB)
gc 15 @0.390s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 76->76->0 MB, 77 MB goal, 4 P
scvg: 0 MB released
scvg: inuse: 0, idle: 163, sys: 163, released: 13, consumed: 150 (MB)
1000000000               0.464 ns/op
PASS
ok      gc_friendly     0.972s

函数执行了1000000000次,GC回收内存次数为15。

注:

博客内容为极客时间视频课《Go语言从入门到实战》学习笔记。
参考课程链接:
https://time.geekbang.org/course/intro/160?code=NHxez89MnqwIfa%2FvqTiTIaYof1kxYhaEs6o2kf3ZxhU%3D&utm_term=SPoster
博客参考代码:
https://github.com/geektime-geekbang/go_learning/tree/master/code/ch49/gc_friendly