Go语言指针性能

7
以下代码展示了两个基准测试。第一个在每次迭代中通过值创建一个结构体,而第二个则使用指向结构体的指针。
为什么后者要慢20倍?我知道GoLang有垃圾回收问题,但逃逸分析不应该处理这些情况吗?
我正在使用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 =)。


2
这两个的机器码生成是什么? - user395760
1
https://gist.github.com/chrisprobst/4c0231c24e1fc4a215cd - Kr0e
2个回答

12

使用-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
1
@Kr0e:逃逸分析存在一些问题,目前已知并且计划在1.5中解决一些。在此之前,如果您的代码非常注重性能,请使用基准测试、-gcflags '-m'或尝试其他编译器。 - Ainar-G
1
好的,谢谢伙计!这个例子本来就很简单,当然在这种情况下使用两个整数更有意义。我只是想了解一下go语言的工作原理(目前)=)。顺便提一下:我犯了一个错误,1.3.3具有不同的性能特征。结果并不更好,只是不同而已。 - Kr0e

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提出问题以来,编译器变得更加智能了 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接