Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Go の GC の不得意な部分を克服したい
Search
鹿
December 15, 2024
Programming
3
1.1k
Go の GC の不得意な 部分を克服したい
Kyoto.go #56 オフライン忘年LT会の発表資料です。
https://kyotogo.connpass.com/event/335437/
鹿
December 15, 2024
Tweet
Share
More Decks by 鹿
See All by 鹿
Golang と Erlang
taiyow
8
2k
Other Decks in Programming
See All in Programming
PRレビューのお供にDanger
stoticdev
1
250
読まないコードリーディング術
hisaju
1
140
CIBMTR振り返り+敗北から学ぶコンペの取り組み方反省
takanao
1
200
Boos Performance and Developer Productivity with Jakarta EE 11
ivargrimstad
0
730
Amazon Bedrockマルチエージェントコラボレーションを諦めてLangGraphに入門してみた
akihisaikeda
1
180
クックパッド検索システム統合/Cookpad Search System Consolidation
giga811
0
190
GoとPHPのインターフェイスの違い
shimabox
2
220
Datadog Workflow Automation で圧倒的価値提供
showwin
1
320
1年目の私に伝えたい!テストコードを怖がらなくなるためのヒント/Tips for not being afraid of test code
push_gawa
1
660
Jasprが凄い話
hyshu
0
190
Webフレームワークとともに利用するWeb components / JSConf.jp おかわり
spring_raining
1
150
フロントエンドオブザーバビリティ on Google Cloud
yunosukey
0
100
Featured
See All Featured
We Have a Design System, Now What?
morganepeng
51
7.4k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
175
52k
How to Think Like a Performance Engineer
csswizardry
22
1.4k
Stop Working from a Prison Cell
hatefulcrawdad
268
20k
Producing Creativity
orderedlist
PRO
344
40k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
Statistics for Hackers
jakevdp
797
220k
Fireside Chat
paigeccino
35
3.2k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
19k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
Raft: Consensus for Rubyists
vanstee
137
6.8k
Transcript
Go の GC の不得意な 部分を克服したい 鹿 @mizushika1
GC とは • Garbage Collection • 作成したヒープを自動で回収してくれる仕組み • ヒープ →
プログラムが動的に確保するメモリのうち、スタックじゃない方 • 関数をまたいだスコープを持つとか、 スタックに乗せられない大きいサイズを確保するときに使う • Runtime 上で動く言語には大抵ある • 無いのは、C/C++, Rust, Zig, Ada など • イメージで言うと、 • 自分が散らかしている部屋をときどき片付けて ゴミを捨ててくれるお手伝いさん
Go の GC • Full mark and sweep • mark:
ヒープ領域全体を検査して、 使われているものから辿れないものを見つける • 検査漏れしないように mark 中はすべての goroutine を停止する → STW (Stop-the-world) • sweep: 使われていないものを解放する • こちらは並行処理可能、プロセスのCPU能力の25%まで使う • 『効率的なGo』 5.5.3 ガベージコレクションが詳しい • イメージで言うと、 • 毎日の掃除で引き出しやタンスの中のものを全部チェックしているサイコパス
世代別 GC というものも有る • GC をする対象を young と old に分けて
GC の頻度を分ける • 普段は young の方だけ GC を実施 • old はときどき or メモリが不足したときだけ実行 • 大抵のオブジェクトは、短命と長命に分かれる傾向がある • すぐ死ぬやつはすぐ死ぬが、死なないやつはずっと生き残る • old は毎回 GC してもほぼ空振りするので頻度を分ける • Go 以外の大抵の GCed 言語が採用 • イメージで言うと、 • 世代別GCは引き出しやタンスの中は大掃除のときだけチェックして、 普段は机や床を片付けるという普通の掃除
Go の GC の得意/不得意 • 得意: • 確保したヒープが早期に解放されるケース • REST
API のようなアクセスがすぐ終了するようなサービス • 一回実行するだけの CLI ツール • 不得意: • gRPC や WebSocket などを使って常時接続するサービス • 1時間以上などの長寿命のコネクション用のヒープが GCで毎回チェックされる • ※Go の GC がリリースごとに改善されているのは知っているが、 それでも常時接続サービスには厳しい面がある • ヒープオブジェクトが1億個あると STW が 1ms 近くになる • どうにか改善(GC負荷を軽減)したい
強い味方1: arena • GC 対象外の特別なヒープ領域を作れる • Go 1.20 に EXPERIMENTAL
で導入された • proposal: arena: new package providing memory arenas #51317 https://github.com/golang/go/issues/51317 • メリット • 長寿命オブジェクトを作っても GC の負荷が増えない • 自力でメモリ確保 (malloc) して割り当てるのに比べてアロケーションが楽 • デメリット • Arena 単位の管理になる • サイズは最低 8MB • Arena 全体の一括解放しかできない • 「長寿命のコネクション単位で arena を作ろう」と思っても、使用サイズが 8MB 未満だと、 メモリ効率が犠牲になる
強い味方1: arena % GOEXPERIMENT=arenas go build … var ar =
arena.NewArena() func allocate(size int) (string, []int) { str := arena.New[string](ar) is := arena.MakeSlice[int](ar, 0, size) return str, is ) // myArena.Free()
強い味方2: sync.Pool • メモリプールを作る標準パッケージ • プールから確保 (Get) して、使い終わったらプールに戻す (Put) •
毎回オブジェクトを生成して GC で回収されるのではなく、同じオブジェクトを使 いまして生成と回収の回数を減らす仕組み • ただし、毎回 GC 対象になるのは • これを、Pool の確保関数内で arena から取得してあとは Pool 側で管理することで、 解放が不要になり arena の「GC の負荷を増やさない」恩恵だけを受けられる • ただし Put を忘れると真の意味でのメモリリークになるので注意
強い味方2: sync.Pool const bufSize = 1024 var ( ar =
arena.NewArena() mu = sync.Mutex{} bufPool = sync.Pool{ New: func() any { mu.Lock() defer mu.Unlock() return arena.MakeSlice[byte](are, 0, bufSize) }, } } func foo() { buf := bufPool.Get().([]byte) defer bufPool.Put(buf) …
まとめ • Go の GC は様々なバランスを考慮した上で世代別 GC を採用していない • それだと長寿命のヒープオブジェクトが多いケースが不得意(GC
負荷が高い) • 回避策として、 arena でヒープを切り出して、sync.Pool でメモリプールを作ると 良さそう • とはいえ arena がまだ EXPERIMENTAL なので使い勝手は未知
おわり
補足:なぜ Go は世代別 GC を採用しないのか • “Getting to Go: The
Journey of Go's Garbage Collector” が参考になる https://go.dev/blog/ismmkeynote • 2018 年の ISMM (メモリ管理関連の学会?) のキーノート • 発表者の Rick Hudson 氏は Google の Go チームで GC とruntime の研究開発者 • 理由: Write Barrier が十分には早くないため • young と old の間の参照を追跡するために write barrier が必要になる • write barrier の遅延は常に(GC期間外にも)かかるので、ヒープが大きくなるほど(GC回数が 減るほど)相対的にコストが大きくなる、が現状では write barrier は速度面で不利 • つまり:世代別 GC の効果は Go では薄い • 世代別 GC を導入すると GC 中の STW を短縮できるが、Go ではこれは現状は大きな問題に なっていない • 世代別 GC を導入しても GC のスループットは向上しない
補足:arena はいつ標準になるの? • Go 1.20 のリリースは 2023/02 … 2年前? まだ
EXPERIMENTAL なの? • 答え:“Likely never” (ほぼ確実にならない) • https://github.com/golang/go/issues/51317#issuecomment-1676334535 • use-after-free 問題のせい • 通常のポインタから arena 内部のデータを指してから arena を解放すると…?
ほんとうにおわり