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
Open source software: how to live long and go far
gaelvaroquaux
0
650
AIの力でお手軽Chrome拡張機能作り
taiseiue
0
180
AIプログラミング雑キャッチアップ
yuheinakasaka
7
1k
1年目の私に伝えたい!テストコードを怖がらなくなるためのヒント/Tips for not being afraid of test code
push_gawa
1
470
昭和の職場からアジャイルの世界へ
kumagoro95
1
410
コミュニティ駆動 AWS CDK ライブラリ「Open Constructs Library」 / community-cdk-library
gotok365
2
180
Code smarter, not harder - How AI Coding Tools Boost Your Productivity | Angular Meetup Berlin
danielsogl
0
100
仕様変更に耐えるための"今の"DRY原則を考える
mkmk884
8
2.7k
PRレビューのお供にDanger
stoticdev
1
220
プログラミング言語学習のススメ / why-do-i-learn-programming-language
yashi8484
0
150
時計仕掛けのCompose
mkeeda
1
310
Writing documentation can be fun with plugin system
okuramasafumi
0
130
Featured
See All Featured
A Tale of Four Properties
chriscoyier
158
23k
Bootstrapping a Software Product
garrettdimon
PRO
306
110k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
6
560
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
19k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
RailsConf 2023
tenderlove
29
1k
KATA
mclloyd
29
14k
Art, The Web, and Tiny UX
lynnandtonic
298
20k
How to Think Like a Performance Engineer
csswizardry
22
1.4k
For a Future-Friendly Web
brad_frost
176
9.5k
Building Your Own Lightsaber
phodgson
104
6.2k
Navigating Team Friction
lara
183
15k
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 を解放すると…?
ほんとうにおわり