GoのGCに関して調べてみた
基本的なところ
- GoのGC
- Concurrent Mark & Sweepを採用している
goroutine はGCされない
goroutine はガーベッジコレクションの対象ではありません。 goroutineはGCされないので、goで起動した関数は必ず終了するように気をつけてプログラムを書きましょう。JavaやPythonの実行中のスレッドがGCに回収されないのと同じですね、自然な仕様だと思います。ただgoroutineは割りと気軽に作成できてしまうので、うっかり新しいgoroutineもGCのルートになることを忘れてしまうかも。
メモリの使用状況の見方
この状態でプログラムをbuildして実行する。プログラムの実行中に、http://localhost:6060/debug/pprof/heap?debug=1 にアクセスすると、プログラムのメモリの使用状況が表示されるので、下の方にあるruntime.MemStatsを調べたりしましょう。runtime.MemStats以外の部分は、goroutineごとのシンボルテーブルっぽく見えるけどあんまり読み方がわかってない。リロードするとその時々のメモリ状況が見れて面白い。
環境変数
GOGC
The GOGC variable sets the initial garbage collection target percentage. A collection is triggered when the ratio of freshly allocated data to live data remaining after the previous collection reaches this percentage. The default is GOGC=100. Setting GOGC=off disables the garbage collector entirely. The runtime/debug package's SetGCPercent function allows changing this percentage at run time. See https://golang.org/pkg/runtime/debug/#SetGCPercent.
- この値をセットすると
- 初期ガベージコレクションの目標パーセンテージを設定出来る
- デフォルトはGOGC=100
- GOGC=offにするとGCを切ることが出来る
ヒープ割り当ての確認
- https://hnakamur.github.io/blog/2018/01/30/go-heap-allocations/
-gcflags -m
をすると割り当てを見ることが出来る-m
を増やすとその分情報が増える
こんな感じで増やせる
go run -gcflags '-m -m' main.go
- 3個まで試したが、3個の時点で情報量がかなり増える
- 2個ぐらいまでがおすすめ
# command-line-arguments ./main.go:7:6: can inline test as: func() *int { v := 1; return &v } ./main.go:3:6: can inline main as: func() { test() } ./main.go:4:6: inlining call to test func() *int { v := 1; return &v } ./main.go:4:6: Before inlining: . CALLFUNC l(4) tc(1) PTR64-*int . . NAME-main.test a(true) l(7) x(0) class(PFUNC) tc(1) used FUNC-func() *int substituting name . NAME-main.v a(true) g(2) l(8) x(0) class(PAUTO) tc(1) addrtaken used int -> . NAME-main.v a(true) l(4) x(0) class(PAUTO) tc(1) addrtaken used int substituting name . NAME-main.v a(true) g(2) l(8) x(0) class(PAUTO) tc(1) addrtaken used int -> . NAME-main.v a(true) l(4) x(0) class(PAUTO) tc(1) addrtaken used int substituting name . NAME-main.v a(true) g(2) l(8) x(0) class(PAUTO) tc(1) addrtaken used int -> . NAME-main.v a(true) l(4) x(0) class(PAUTO) tc(1) addrtaken used int ./main.go:4:6: After inlining . INLCALL-init . . DCL l(4) . . . NAME-main.~r0 a(true) l(4) x(0) class(PAUTO) tc(1) assigned used PTR64-*int . . AS l(4) tc(1) . . . NAME-main.~r0 a(true) l(4) x(0) class(PAUTO) tc(1) assigned used PTR64-*int . INLCALL l(4) tc(1) PTR64-*int . INLCALL-rlist . . NAME-main.~r0 a(true) l(4) x(0) class(PAUTO) tc(1) assigned used PTR64-*int . INLCALL-body . . AS-init . . . DCL l(8) . . . . NAME-main.v a(true) l(4) x(0) class(PAUTO) tc(1) addrtaken used int . . AS l(8) colas(true) tc(1) . . . NAME-main.v a(true) l(4) x(0) class(PAUTO) tc(1) addrtaken used int . . . LITERAL-1 l(8) tc(1) int . . GOTO-init . . . AS2 l(4) tc(1) . . . AS2-list . . . . NAME-main.~r0 a(true) l(4) x(0) class(PAUTO) tc(1) assigned used PTR64-*int . . . AS2-rlist . . . . ADDR l(9) tc(1) PTR64-*int . . . . . NAME-main.v a(true) l(4) x(0) class(PAUTO) tc(1) addrtaken used int . . GOTO l(4) tc(1) . . . NAME-main..i0 a(true) l(4) x(0) . . LABEL l(4) tc(1) . . . NAME-main..i0 a(true) l(4) x(0) ./main.go:4:6:[1] main esc: ~r0 ./main.go:4:6:[1] main esc: var ~r0 *int ./main.go:4:6:[1] main esc: ~r0 ./main.go:4:6:[1] main esc: ~r0 = <N> ./main.go:4:6:[1] main esc: v ./main.go:4:6:[1] main esc: var v int ./main.go:4:6:[1] main esc: v ./main.go:4:6:[1] main esc: 1 ./main.go:4:6:[1] main esc: v := 1 ./main.go:4:6:[1] main escassign: v( a(true) l(4) x(0) class(PAUTO) ld(1) tc(1) addrtaken used)[NAME] = 1( l(8) tc(1))[LITERAL] ./main.go:4:6:[1] main esc: ~r0 ./main.go:4:6:[1] main esc: v ./main.go:4:6:[1] main esc: &v ./main.go:4:6:[1] main esc: ~r0 = &v ./main.go:4:6:[1] main escassign: ~r0( a(true) l(4) x(0) class(PAUTO) ld(1) tc(1) assigned used)[NAME] = &v( l(9) esc(no) ld(1) tc(1))[&] ./main.go:4:6:[1] main esc: .i0 ./main.go:4:6:[1] main esc: goto .i0 ./main.go:4:6:[1] main esc: .i0 ./main.go:4:6:[1] main esc: .i0: ./main.go:4:6:.i0: non-looping label ./main.go:4:6:[1] main esc: <node BLOCK> escflood:0: dst ~r0 scope:main[1] escwalk: level:{0 0} depth:0 op=& &v( l(9) esc(no) ld(1) tc(1)) scope:main[1] extraloopdepth=-1 escwalk: level:{-1 -1} depth:1 op=NAME v( a(true) l(4) x(0) class(PAUTO) ld(1) tc(1) addrtaken used) scope:main[1] extraloopdepth=-1 ./main.go:4:6: main &v does not escape ./main.go:8:2:[1] test esc: v ./main.go:8:2:[1] test esc: var v int ./main.go:8:4:[1] test esc: v ./main.go:8:7:[1] test esc: 1 ./main.go:8:4:[1] test esc: v := 1 ./main.go:8:4:[1] test escassign: v( a(true) g(2) l(8) x(0) class(PAUTO) ld(1) tc(1) addrtaken used)[NAME] = 1( l(8) tc(1))[LITERAL] ./main.go:9:9:[1] test esc: v ./main.go:9:9:[1] test esc: &v ./main.go:9:2:[1] test esc: return &v ./main.go:9:2:[1] test escassign: ~r0( a(true) g(1) l(7) x(0) class(PPARAMOUT))[NAME] = &v( l(9) esc(no) ld(1) tc(1))[&] escflood:0: dst ~r0 scope:test[0] escwalk: level:{0 0} depth:0 op=& &v( l(9) esc(no) ld(1) tc(1)) scope:test[1] extraloopdepth=-1 ./main.go:9:9: &v escapes to heap, level={0 0}, dst=~r0 dst.eld=0, src.eld=1 ./main.go:8:2: moved to heap: v escwalk: level:{-1 -1} depth:1 op=NAME v( a(true) g(2) l(8) x(0) class(PAUTOHEAP) esc(h) ld(1) tc(1) addrtaken used) scope:test[1] extraloopdepth=1
freeosmemoryについて
- https://stackoverflow.com/questions/42345060/freeosmemory-in-production
- ここの議論にもあるが、本番環境でこれの実行はしない方がいいと思う
参考
- https://golang.org/pkg/runtime/
- https://dave.cheney.net/2014/07/11/visualising-the-go-garbage-collector
- https://engineering.linecorp.com/ja/blog/go-gc/
- https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/cms.html
- http://cco.hatenablog.jp/entry/2014/12/01/162240
- http://www.nminoru.jp/~nminoru/java/cms/concurrent_mark_sweep.html
- https://postd.cc/golangs-real-time-gc-in-theory-and-practice/
- https://www.yunabe.jp/docs/golang_pitfall.html
- https://blog.kazu69.net/2017/08/20/memory-management-go/
- http://ascii.jp/elem/000/001/496/1496211/
- https://www.keicode.com/windows/win11.php
- https://hnakamur.github.io/blog/2018/01/30/go-heap-allocations/
- https://stackoverflow.com/questions/24376817/go-1-3-garbage-collector-not-releasing-server-memory-back-to-system
- https://stackoverflow.com/questions/42345060/freeosmemory-in-production
- https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/
- https://qiita.com/KoyaFukushi/items/fc532d7dae29381e8186
- https://blog.learngoprogramming.com/a-visual-guide-to-golang-memory-allocator-from-ground-up-e132258453ed