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
4
1.5k
Go の GC の不得意な 部分を克服したい
Kyoto.go #56 オフライン忘年LT会の発表資料です。
https://kyotogo.connpass.com/event/335437/
鹿
December 15, 2024
Tweet
Share
More Decks by 鹿
See All by 鹿
なぜselectはselectではないのか
taiyow
1
410
Golang と Erlang
taiyow
8
2.2k
Other Decks in Programming
See All in Programming
組織で育むオブザーバビリティ
ryota_hnk
0
180
AIに仕事を丸投げしたら、本当に楽になれるのか
dip_tech
PRO
0
110
ぼくの開発環境2026
yuzneri
0
260
Rust 製のコードエディタ “Zed” を使ってみた
nearme_tech
PRO
0
230
日本だけで解禁されているアプリ起動の方法
ryunakayama
0
320
AIで開発はどれくらい加速したのか?AIエージェントによるコード生成を、現場の評価と研究開発の評価の両面からdeep diveしてみる
daisuketakeda
1
2.5k
CSC307 Lecture 07
javiergs
PRO
1
560
今から始めるClaude Code超入門
448jp
8
9.2k
AgentCoreとHuman in the Loop
har1101
5
260
副作用をどこに置くか問題:オブジェクト指向で整理する設計判断ツリー
koxya
1
620
Unicodeどうしてる? PHPから見たUnicode対応と他言語での対応についてのお伺い
youkidearitai
PRO
1
2.6k
Sekiban + Microsoft Orleans のアクターをAWS対応しました / Sekiban + Microsoft Orleans actors are now supported on AWS.
tomohisa
0
110
Featured
See All Featured
Exploring anti-patterns in Rails
aemeredith
2
260
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
The untapped power of vector embeddings
frankvandijk
1
1.6k
New Earth Scene 8
popppiees
1
1.6k
Fashionably flexible responsive web design (full day workshop)
malarkey
408
66k
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
120
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
5
740
4 Signs Your Business is Dying
shpigford
187
22k
Designing for Timeless Needs
cassininazir
0
130
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
1.9k
First, design no harm
axbom
PRO
2
1.1k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
68
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 を解放すると…?
ほんとうにおわり