囲碁がGC問題の引き金になる場合の話
初期の頃によく蔑まれたのが、ガベージコレクション(以後:GC)の仕組みでSTW(Stop-The-World)時間が長いことでした。そこで、現時点では、STWの開始として、Go言語がGCを発動するのはいつなのか?
1. GCとは何ですか?
コンピュータサイエンスにおいて、ガベージコレクション(GC)は、プログラムで使われなくなったオブジェクトとそれが占めるメモリをガベージコレクタが回収しようとする自動メモリ管理のメカニズムである。
最も古い
John McCarthy
を簡略化するために、1959年頃にガベージコレクションが発明されました。
Lisp
という、手動でメモリを管理する仕組みがあります(@wikipediaより)。
2. なぜGCなのか
手動でメモリを管理するのは面倒ですし、メモリの管理を誤ったり足りなくなったりすると、プログラムが不安定になったり(リークが続いたり)、あからさまにクラッシュしたりすることに直結します。
3. GCトリガーシナリオ
GCトリガーのシナリオは、以下の2つに大別されます。
- システムトリガー ランタイム自身が内蔵の条件に基づいてチェック、検出、そしてGCを行い、アプリケーション全体の可用性を維持します。
-
手動によるトリガー
: 開発者がビジネスコード自体で呼び出す
runtime.GC
メソッドを使用して GC 動作をトリガーします。
3.1 システムトリガー
システムトリガーシナリオでは、Goソースコードの
src/runtime/mgc.go
ファイルを作成します。
は、GCのシステムトリガーについて、以下の3つのシナリオを明示的に特定しています。
const (
gcTriggerHeap gcTriggerKind = iota
gcTriggerTime
gcTriggerCycle
)
-
gcTriggerHeap.
割り当てられたヒープサイズが閾値(コントローラで計算されたトリガーヒープのサイズ)に達するとトリガーされます。 -
gcTriggerTime.
最後のGCサイクルから一定時間が経過したときにトリガーされる。時間サイクルの始まりはruntime.forcegcperiod
変数で指定され、デフォルトは2分です。 -
gcTriggerCycle.
GCがオンになっていない場合、GCを開始します。
手動でトリガーされた
runtime.GC
メソッドが関与しています。
3.2 手動トリガー
手動トリガーのシナリオでは、唯一のGo言語である
runtime.GC
メソッドをトリガーすることができ、分類するための余分なものは何もありません。
しかし、どのようなビジネスシナリオが、通常、GCに手動で干渉し、強制的に起動させることになるのか、考えなければなりません。
手動で強制的にトリガーをかける必要があるシナリオは、極めて稀です。あるビジネスメソッドが実行された後、メモリを使いすぎたために人為的に解放する必要がある場合です。あるいは
debug
がプログラムによって要求される。
3.3 基本的な流れ
GoがGCをトリガーするシナリオを理解した後、GCをトリガーするフローコードがどのように見えるか、手動でトリガーされる
runtime.GC
メソッドを突破口にします。
コアとなるコードは以下の通りです。
func GC() {
n := atomic.Load(&work.cycles)
gcWaitOnMark(n)
gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})
gcWaitOnMark(n + 1)
for atomic.Load(&work.cycles) == n+1 && sweepone() ! = ^uintptr(0) {
sweep.nbgsweep++
Gosched()
}
for atomic.Load(&work.cycles) == n+1 && atomic.Load(&mheap_.sweepers) ! = 0 {
Gosched()
}
mp := acquirem()
cycle := atomic.Load(&work.cycles)
if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) {
mProf_PostSweep()
}
releasem(mp)
}
新しいGCサイクルを開始する前に
gcWaitOnMark
メソッドを使用して、前のGCラウンドの終了をマークします(スキャン終了、マーク、マーク終了などによる)。
を呼び出して新しいGCサイクルを開始します。
gcStart
メソッドはGCの動作をトリガーし、スキャンマーカーフェーズを開始します。
を呼び出す必要があります。
gcWaitOnMark
メソッドを使用して、現在のGCサイクルのスキャン、マーク、マーク終了が完了するまで待ちます。
を呼び出す必要があります。
sweepone
メソッドを使用して、掃引されていないヒープスパンをスキャンし、クリーンアップが完了するように掃引を続けます。掃引が完了するのを待つまでのブロック時間中に
Gosched
を出すようにします。
mProf_PostSweepメソッドは、GCの現在のラウンドがほぼ完了した後に呼び出されます。これは、最後のマーカー終了時のヒーププロファイルのスナップショットを記録します。
Mの解放で終了。
3.4 トリガーとなる場所
GCの基本的な流れを見て、基本的な理解はできました。でも、もしかしたら、また疑問に思った方もいらっしゃるかもしれませんね?
今回のタイトルは "GCはいつ発動するのか"ですが、先に発動するタイミングは分かっています。しかし......Goはトリガーする仕組みをどこに実装しているのでしょうか、フローから完全に抜け落ちているように思えますが......。
4. スレッドの監視
要するに、Goランタイムでは
(runtime)
が初期化されると
goroutine
GC機構に関連することを処理する。
そのコードは以下の通りです。
func init() {
go forcegchelper()
}
func forcegchelper() {
forcegc.g = getg()
lockInit(&forcegc.lock, lockRankForcegc)
for {
lock(&forcegc.lock)
if forcegc.idle ! = 0 {
throw("forcegc: phase error")
}
atomic.Store(&forcegc.idle, 1)
goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)
// this goroutine is explicitly resumed by sysmon
if debug.gctrace > 0 {
println("GC forced")
}
gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
}
}
このプログラムにおいて、特に気になるのは
forcegchelper
メソッドを呼び出します。
goparkunlock
メソッドで
goroutine
を休眠待機状態にし、不要なリソースのオーバーヘッドを削減します。
ハイバネーション後
sysmon
これは、監視や目覚ましなどの動作を行うシステム監視スレッドである
func sysmon() {
...
for {
...
// check if we need to force a GC
if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) ! = 0 {
lock(&forcegc.lock)
forcegc.idle = 0
var list gList
list.push(forcegc.g)
injectglist(&list)
unlock(&forcegc.lock)
}
if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {
lasttrace = now
schedtrace(debug.scheddetail > 0)
}
unlock(&sched.sysmonlock)
}
}
このコードの核となる動作は、forループを繰り返し、その中で
gcTriggerTime
と
now
変数を使用して、一定の時間に達したかどうかを判断します(デフォルトは2分)。
reachedが条件を満たしたことを意味する場合
forcegc.g
をグローバルキューに入れ、新しいラウンドのスケジューリングを受け、上記の
forcegchelper
上記
5. ヒープメモリの要求
タイミングトリガーの仕組みを理解した上で、もう一つのシナリオはヒープ領域が確保される時なので、どこを見るかはかなり明確です。
これは、ヒープメモリの実行時要求である
mallocgc
メソッドを使用します。コアとなるコードは以下の通りです。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
shouldhelpgc := false
...
if size <= maxSmallSize {
if noscan && size < maxTinySize {
...
// Allocate a new maxTinySize block.
span = c.alloc[tinySpanClass]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(tinySpanClass)
}
...
spc := makeSpanClass(sizeclass, noscan)
span = c.alloc[spc]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(spc)
}
...
}
} else {
shouldhelpgc = true
span = c.allocLarge(size, needzero, noscan)
...
}
if shouldhelpgc {
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x
}
小型オブジェクト
: スモールオブジェクトを要求したときに、メモリに空きスパンがないことがわかったら
nextFree
メソッドで新しい利用可能なオブジェクトを取得する必要があり、GC 動作を引き起こす可能性があります。
大きなオブジェクト 32kを超えるラージオブジェクトが要求された場合、GC動作が発生する場合があります。
まとめ
今回は、Go言語をトリガーとしたGCのシナリオを大きく2つに分類して紹介し、それぞれを大分類の中のシナリオのサブセットを基に説明しました。
GoがGCを誘発する場合についての話はこれで終わりです。GoがGCを誘発する場合についての詳しい情報は、Scripting Houseの過去の記事を検索するか、以下の記事を引き続き閲覧してください。
関連
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン