Golang转义分析

首先

Go可以很好地处理堆栈和堆内存。

但是,由于栈比堆更快,所以尽量希望使用栈。

因此,我们可以尝试减少堆分配,并同时注意转义分析。

逃逸分析是什么?

输出逃逸分析

go build gcflags="-m -l" sample.go
go test gcflags="-m -l" -bench=.

-m エスケープ解析を出力

-l 関数のインライン化をオフにする (今回はより簡単な例で説明したいのでオフにする。インライン化の影響でエスケープされないことがあるので)

实践

以下为例1的中文本地化释义:
例1:- “I went to the supermarket to buy some groceries yesterday.”
我昨天去了超市买了一些食品杂货。

我将首先执行下面的基准测试。

package main

import "testing"

func Escape() *int {
    v := 1000
    return &v
}

func NoEscape() int {
    v := 1000
    return v
}

func BenchmarkEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = Escape()
    }
}

func BenchmarkNoEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = NoEscape()
    }
}

结果↓


$ go test -bench=. -gcflags "-m -l"
# github.com/Kooooya/ground/escape_analysis
./01_test.go:7: &v escapes to heap
./01_test.go:6: moved to heap: v
.....
BenchmarkNoEscape-4     2000000000           1.57 ns/op
BenchmarkEscape-4       100000000           15.3 ns/op

逃避的速度比较慢大约十倍。

./01_test.go:7: &v escapes to heap
./01_test.go:6: moved to heap: v

这是一个关于将v分配(逃逸)到堆上的日志。
如果没有将其分配到堆上,BenchmarkEscape将无法引用第6行的v的值。

请问您能以中文母语进行再次解释吗?只需一种选项。


package main

import "testing"

func Escape() *int {
        s := []int{1, 2, 3, 4, 5}
        y := &s[0]
        return y
}

func NoEscape() int {
        s := []int{1, 2, 3, 4, 5}
        y := s[0]
        return y
}

func BenchmarkEscape(b *testing.B) {
        for i := 0; i < b.N; i++ {
                Escape()
        }
}

func BenchmarkNoEscape(b *testing.B) {
        for i := 0; i < b.N; i++ {
                NoEscape()
        }
}
# command-line-arguments
./02_test.go:7: &s[0] escapes to heap
./02_test.go:6: []int literal escapes to heap
./02_test.go:12: NoEscape []int literal does not escape
.....
BenchmarkEscape-4       50000000            27.9 ns/op
BenchmarkNoEscape-4     500000000            3.33 ns/op

差距大约为8倍左右。
值得注意的是

./02_test.go:7: &s[0] escapes to heap
./02_test.go:6: []int literal escapes to heap

切片已经被转义。
在Go语言中,如果切片中的任何一个元素被转义,整个切片都会被转义。

因此,可以将函数返回值与切片的元素放入函数的局部变量中,并将该指针返回,以减少逃逸值的大小,从而提高速度。


package main

import "testing"

func LittleEscape() *int {
    s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3,......}
    y := s[0]
    return &y
}

func Escape() *int {
    s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3,......}
    return &s[0]
}

func BenchmarkLitteEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        LittleEscape()
    }
}

func BenchmarkEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Escape()
    }
}
./02_test.go:8: &y escapes to heap
./02_test.go:7: moved to heap: y
./02_test.go:6: LittleEscape []int literal does not escape
./02_test.go:13: &s[0] escapes to heap
./02_test.go:12: []int literal escapes to heap
.....
BenchmarkLitteEscape-4        500000          3104 ns/op
BenchmarkEscape-4             100000         11782 ns/op

请原生中文翻译以下内容,只需要一种选项:

Example 3

在map中设置的指针肯定会被逃逸。


package main

import "testing"

func Escape() {
    s := map[string]*int{}
    i := 1
    s["a"] = &i
}

func NoEscape() {
    s := map[string]int{}
    i := 1
    s["a"] = i
}

func BenchmarkEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Escape()
    }
}

func BenchmarkNoEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NoEscape()
    }
}

./04_test.go:8: &i escapes to heap
./04_test.go:7: moved to heap: i
./04_test.go:6: Escape map[string]*int literal does not escape
./04_test.go:12: NoEscape map[string]int literal does not escape
.....
BenchmarkEscape-4       10000000           203 ns/op
BenchmarkNoEscape-4     10000000           188 ns/op

请提供具体的上下文或原文,以便正确理解您的请求。

无论是在 slice 中还是在设定指针时,指针都会被确保逃逸。


package main

import "testing"

func Escape() {
    s := make([]*int, 1)
    i := 1
    s[0] = &i
}

func NoEscape() {
    s := make([]int, 1)
    i := 1
    s[0] = i
}

func BenchmarkEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Escape()
    }
}

func BenchmarkNoEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NoEscape()
    }
}
.....
./05_test.go:8: &i escapes to heap
./05_test.go:7: moved to heap: i
./05_test.go:6: Escape make([]*int, 1) does not escape
./05_test.go:12: NoEscape make([]int, 1) does not escape
.....
BenchmarkEscape-4       100000000           21.0 ns/op
BenchmarkNoEscape-4     300000000            4.72 ns/op

这里还是相当不同啊。

请提供完整的例5的文本,然后我可以为您进行适当的中文转述。

即使是在结构体的字段中,也会发生转义。


package main

import "testing"

type S struct {
    M *int
}

func Escape(y int) (z S) {
    z.M = &y
    return z
}

func NoEscape(y *int) (z S) {
    z.M = y
    return z
}

func BenchmarkEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Escape(i)
    }
}

func BenchmarkNoEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NoEscape(&i)
    }
}

# command-line-arguments
./06_test.go:10: &y escapes to heap
./06_test.go:9: moved to heap: y
./06_test.go:14: leaking param: y to result z level=0
.....
BenchmarkEscape-4       100000000           17.4 ns/op
BenchmarkNoEscape-4     500000000            4.29 ns/op

为什么下面的函数不会泄露呢?


func NoEscape(y *int) (z S) {
    z.M = y
    return z
}

因为y的值存储在前一个栈帧内,所以即使不在堆上也知道它的信息。

On May 28th, Kevin’s birthday party will be held at his house from 7pm to 10pm. There will be snacks, drinks, and games. Please RSVP by May 25th if you can attend.


package main

import "testing"

type S struct {
    M *int
}

func Escape(y *int, z *S) {
    z.M = y
}

func NoEscape(y *int) (z S) {
    z.M = y
    return z
}

func BenchmarkEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var s S
        Escape(&i, &s)
    }
}

func BenchmarkNoEscape(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NoEscape(&i)
    }
}
.....
./07_test.go:9: leaking param: y
./07_test.go:9: Escape z does not escape
./07_test.go:13: leaking param: y to result z level=0
./07_test.go:21: &i escapes to heap
./07_test.go:19: moved to heap: i
.....
BenchmarkEscape-4       200000000            8.18 ns/op
BenchmarkNoEscape-4     500000000            2.97 ns/op

在例子5中,我解释了它是前一个堆栈帧的变量。


func Escape(y *int, z *S) {
        z.M = y
}

由于Go编译器只能通过输入和输出的流来进行解析,因此此处会被转义。

总结

    • gcflags=”-m -l”

 

    • GCある言語でも、完全にメモリのことを把握しなくてよいわけではない

 

    スタック使ってても遅い場合がある。ベンチマークを取る

链接

    • http://blog.rocana.com/golang-escape-analysis

 

    https://docs.google.com/document/d/1CxgUBPlx9iJzkz9JWkb6tIpTe5q32QDmz8l0BouG0Cw/preview
广告
将在 10 秒后关闭
bannerAds