1.问题

调用一个函数,如果入参是一个 struct,我们应该使用值传递还是指针传递呢?

2.性能分析

假设有一个 student struct,记录一个学生的基本信息。

type student struct {
    id   int
    name string
    sex  byte
    age  int
    addr string
}

假设有两个函数分别采用值传递和指针传递。

func PassByValue(s student) {
    // Nothing to do.
}

func PassByPtr(s student) {
    // Nothing to do.
}

然后写两个基准测试环境,放到 _test.go 结尾的测试文件中。

func BenchmarkPassByVal(b *testing.B) {
    var s student
    for i := 0; i < b.N; i++ {
        PassByVal(s)
    }
}

func BenchmarkPassByPtr(b *testing.B) {
    var s student
    for i := 0; i < b.N; i++ {
        PassByPtr(&s)
    }
}

假设将上面的代码放到 pass 目录下。下面我们执行基准测试命令,看下使用值传递和指针传递的性能差别。

go test -bench=^BenchmarkPass -benchmem -gcflags="-l" main/pass
goos: windows
goarch: amd64
pkg: main/pass
cpu: 11th Gen Intel(R) Core(TM) i5-11500 @ 2.70GHz
BenchmarkPassByVal-12           873122876                1.333 ns/op           0 B/op          0 allocs/op
BenchmarkPassByPtr-12           1000000000               0.6241 ns/op          0 B/op          0 allocs/op
PASS
ok      main/pass       2.215s

-bench=^BenchmarkPass 指定要测试的基准函数名称以 BenchmarkPass 开头。-benchmem 打印基准测试的内存分配统计信息。-gcflags="-l" 表示禁止编译器的内联优化。

可见,当使用值传递 struct,因为发生拷贝的情况,所以性能比指针传递要低。

可以预见的是,随着 struct 大小增加,值传递和指针传递的性能差距会越来越大。

比如在上面的 student 中加入一个长度为 1024 字符的数组。

type student struct {
    id   int
    name string
    sex  byte
    age  int
    addr string
    selfEvaluation [1024]rune
}

再次看下一下值传递和指针传递的性能差距。

go test -bench=^BenchmarkPass -benchmem -gcflags="-l" main/pass
goos: windows
goarch: amd64
pkg: main/pass
cpu: 11th Gen Intel(R) Core(TM) i5-11500 @ 2.70GHz
BenchmarkPassByVal-12           46635990                24.18 ns/op            0 B/op          0 allocs/op
BenchmarkPassByPtr-12           1000000000               0.6894 ns/op          0 B/op          0 allocs/op
PASS
ok      main/pass       2.151s

可见,随着 struct 大小增加,值传递和指针传递的性能差距会越来越大。

3.结论

在 Golang 中,struct 可以使用值传递或指针传递进行传递。使用值传递时,函数会创建一个 struct 值的副本,并将该副本传递给函数。使用指针传递时,函数会传递 struct 值的指针。

使用指针传递 struct 通常比使用值传递更有效率,因为在值传递时需要将整个 struct 复制一份,并将其传递给函数。这可能会导致内存使用量增加,并且如果 struct 很大,可能会导致性能下降。

如果你需要在函数中修改 struct 的值,则必须使用指针传递。这是因为使用值传递传递 struct 副本时,对副本的任何更改都不会影响原始 struct。

如果你不需要在函数中修改 struct 的值,基于性能的考虑,建议使用指针传递。如果 struct 大小不大于指针的大小(如在 64 位系统上的 8 字节),那么就用值传递。

以上结论不仅仅适用于 struct,对于所有的大类型(如数组等),在选择值传递还是指针传递时,都应该考虑值拷贝带来的性能开销。

powered by Gitbook该文章修订时间: 2024-03-22 15:30:00

results matching ""

    No results matching ""