雑なメモ書き

気楽にいきます

GoのGCに関して調べてみた

基本的なところ

  • GoのGC
  • Concurrent Mark & Sweepを採用している

goroutine はGCされない

goroutine はガーベッジコレクションの対象ではありません。 goroutineはGCされないので、goで起動した関数は必ず終了するように気をつけてプログラムを書きましょう。JavaPythonの実行中のスレッドが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を切ることが出来る

ヒープ割り当ての確認

こんな感じで増やせる

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について

参考