【问题标题】:GoLang Pointer PerformanceGoLang 指针性能
【发布时间】:2026-01-16 00:40:01
【问题描述】:

以下代码显示了两个基准。第一个在每次迭代中按值创建一个结构,而第二个确实使用指向该结构的指针。

为什么后者慢 20 倍?我知道 GoLang 的 GC 问题,但不应该逃避分析处理这些情况吗?

我正在使用 go1.4beta1,但 1.3.3 给了我[相同 - 错误]不同的结果。

有什么想法吗?

package main

import "testing"

type Adder struct {
    vals []int
}

func (a *Adder) add() int {
    return a.vals[0] + a.vals[1]
}

func BenchmarkWithoutPointer(b *testing.B) {
    accum := 0
    for i := 0; i < b.N; i++ {
        adder := Adder{[]int{accum, i}}
        accum = adder.add()
    }
    _ = accum
}

func BenchmarkWithPointer(b *testing.B) {
    accum := 0
    for i := 0; i < b.N; i++ {
        adder := &Adder{[]int{accum, i}}
        accum = adder.add()
    }
    _ = accum
}

基准 go1.4.1:

 $ go test -bench=.                                                                                                                             

testing: warning: no tests to run
PASS
BenchmarkWithoutPointer 1000000000           2.92 ns/op
BenchmarkWithPointer    30000000            57.8 ns/op
ok      github.com/XXXXXXXXXX/bench/perf    5.010s

基准 go1.3.3:

testing: warning: no tests to run
PASS
BenchmarkWithoutPointer 500000000            7.89 ns/op
BenchmarkWithPointer    50000000            37.5 ns/op
ok      

编辑:

结论:

正如 Ainar-G 所说,[]int 确实在第二个基准测试中逃逸到堆中。在阅读了更多关于 1.4beta1 的信息之后,似乎在访问由新 GC 计划引起的堆时引入了新的写屏障。但原始执行似乎有所增加。期待1.5 =)。

【问题讨论】:

标签: performance memory go stack benchmarking


【解决方案1】:

使用-m gcflag 运行基准测试给出了可能的答案:

./main_test.go:16: BenchmarkWithoutPointer []int literal does not escape
(...)
./main_test.go:25: []int literal escapes to heap

您在第二个示例中的[]int 转义到堆,这比堆栈慢。如果您使用单独的 xy 字段作为参数而不是切片

type Adder struct {
    x, y int
}

func (a *Adder) add() int {
    return a.x + a.y
}

基准测试显示了预期的行为:

BenchmarkWithoutPointer 1000000000               2.27 ns/op
BenchmarkWithPointer    2000000000               1.98 ns/op

【讨论】:

  • 我明白了,我只是查看了机器代码。第二个函数包含一个“0x00d8 00216 (bench_test.go:25) CALL ,runtime.writebarrierslice(SB)”调用。你知道为什么 int 切片被认为是转义的吗?在我看来,这并不是真正的“逃避”。
  • @Kr0e 有issues 用逃逸分析。它们是已知的,有些计划在 1.5 中修复。在此之前,如果您的代码对性能非常敏感,请使用基准测试,-gcflags '-m',或尝试不同的编译器。
  • 好的,谢谢伙计!无论如何,这个例子是微不足道的,当然在这种情况下使用两个整数更有意义。我只是想了解 go 是如何工作的(目前)=)。仅供参考:我做错了,1.3.3 具有不同的性能特征。结果不是更好,只是不同。
【解决方案2】:

使用 go1.16.7 运行原始帖子中的确切代码,指针版本现在的速度大致相同(略快)

$ go test -bench=.
goos: linux
goarch: amd64
pkg: example.com/x
cpu: Intel(R) Core(TM) i7-9850H CPU @ 2.60GHz
BenchmarkWithoutPointer-12      945450447            1.212 ns/op
BenchmarkWithPointer-12         965921562            1.199 ns/op
PASS
ok      example.com/x   2.562s

所以自从 OP 提出他的问题以来,编译器变得更聪明了 :)

【讨论】: