質問
golang の メモリエスケープ についてご存知ですか?メモリエスケープはどのような状況で発生しますか?
回答方法
golang プログラム変数
には、実行時にそのライフサイクル全体が完全に把握されているかどうかを証明する一連の検証データが含まれます。変数がこれらのチェックに合格すると、 スタックに
を割り当てることができます。それ以外の場合は、エスケープ
と呼ばれ、ヒープに
を割り当てる必要があります。
変数がヒープにエスケープされる一般的な状況:
- メソッドでローカル変数ポインターを返す ローカル変数本来はスタックに割り当て、スタックに割り当てを解除する必要があります。ただし、復帰時に外部参照するため、スタックよりもライフサイクルが長く、オーバーフローしてしまいます。
- ポインタまたはポインタを含む値をチャネルに送信します。 コンパイル時には、どのゴルーチンがチャネル上のデータを受信するかを知る方法はありません。したがって、コンパイラには変数がいつ解放されるかを知る方法がありません。
- ポインターまたはポインターを含む値をスライスに格納します。 典型的な例は []*string です。これにより、スライスの内容が逃げてしまいます。その背後にある配列はスタック上に割り当てられますが、それが参照する値はヒープ上にある必要があります。
- スライスの背後にある配列は、追加中にその容量 (上限) を超える可能性があるため、再割り当てされます。 スライスが初期化される場所はコンパイル時にわかり、最初はスタック上に割り当てられます。スライスの背後にあるストレージが実行時データに基づいて拡張される場合、それはヒープ上に割り当てられます。
- インターフェイス型のメソッドを呼び出します。 インターフェイス型のメソッドの呼び出しは動的にスケジュールされます。メソッドの実際の実装は実行時にのみわかります。 io.Reader 型の変数 r を想像してください。r.Read(b) を呼び出すと、r の値とスライス b の背後にあるストレージがエスケープされ、ヒープ上に割り当てられます。
例
- 例で理解を深めてから、
go build -gcflags=-m
で脱出状況を確認してみます。
package main import "fmt" type A struct { s string } // 这是上面提到的 "在方法内把局部变量指针返回" 的情况 func foo(s string) *A { a := new(A) a.s = s return a //返回局部变量a,在C语言中妥妥野指针,但在go则ok,但a会逃逸到堆 } func main() { a := foo("hello") b := a.s + " world" c := b + "!" fmt.Println(c) }
ログイン後にコピー
実行go build -gcflags=-m main.go
go build -gcflags=-m main.go # command-line-arguments ./main.go:7:6: can inline foo ./main.go:13:10: inlining call to foo ./main.go:16:13: inlining call to fmt.Println /var/folders/45/qx9lfw2s2zzgvhzg3mtzkwzc0000gn/T/go-build409982591/b001/_gomod_.go:6:6: can inline init.0 ./main.go:7:10: leaking param: s ./main.go:8:10: new(A) escapes to heap ./main.go:16:13: io.Writer(os.Stdout) escapes to heap ./main.go:16:13: c escapes to heap ./main.go:15:9: b + "!" escapes to heap ./main.go:13:10: main new(A) does not escape ./main.go:14:11: main a.s + " world" does not escape ./main.go:16:13: main []interface {} literal does not escape <autogenerated>:1: os.(*File).close .this does not escape
ログイン後にコピー
-
./main.go:8:10: new(A) はヒープにエスケープします
説明new(A)
はエスケープします。これは、上で説明した最初の一般的な状況に準拠します。 -
./main.go:14:11: main a.s " world" はエスケープされません
説明b
変数はメソッド内にのみ存在するため、エスケープされません。メソッドの最後にリサイクルされます。 -
./main.go:15:9: b "!" はヒープにエスケープ
説明c
変数はfmt.Println(a . . Interface{})
出力された変数はすべてエスケープされます。興味のある方は理由を確認してください。 - 上記の操作は、実際には エスケープ分析 と呼ばれます。 次回の記事では、変数のエスケープを防ぐために、よりトリッキーな方法を使用する方法について説明します。面接官の前で皆が自慢できるので便利です。