導入
Go言語の最大の武器である「Goroutine」は、軽量で非常に強力ですが、時に「なぜか処理が遅い」「CPUを使い切れていない気がする」といった原因不明のパフォーマンス問題に直面することがあります。そんな時、闇雲にコードを修正するのではなく、Goランタイムが裏でどう動いているかを可視化するのが解決への近道です。今回は、環境変数「GODEBUG=schedtrace」を使って、Goroutineのスケジューリング状況をリアルタイムに追跡する方法を解説します。
基礎知識
Goのランタイムには、数万単位のGoroutineを効率よくCPUコアに割り当てる「スケジューラ」が組み込まれています。このスケジューラは、実行待ちの状態(Runnable)や実行中(Running)のGoroutineを管理し、マルチコア環境で最大限のパフォーマンスが出るよう自動的に制御しています。GODEBUG=schedtraceを有効にすると、このスケジューラの状態を標準エラー出力(stderr)に一定間隔で書き出せるようになります。
実装/解決策
特別なライブラリをインストールする必要はありません。プログラムを実行する際に、環境変数を指定するだけです。
コマンド例:
GODEBUG=schedtrace=1000 ./your-program
ここで「1000」はミリ秒単位の出力間隔です。これを実行すると、コンソールに以下のような情報が流れます。
SCHED 1000ms: gomaxprocs=8 idleprocs=6 threads=10 spinningthreads=0 idlethreads=4 runq=0 [0 0 0 0 0 0 0 0]
・gomaxprocs: 使用可能な論理プロセッサ数
・idleprocs: 待機中のプロセッサ数
・runq: 全体的な実行待ちキューの長さ
・[0 0 0 0 …]: 各プロセッサごとの実行待ちキューの状況
サンプルプログラム
以下のコードを実行し、環境変数を付与して動作を確認してみてください。わざとGoroutineを大量生成し、スケジューラが頑張っている様子を観察します。
package main
import (
“fmt”
“time”
)
func main() {
// 負荷をかけるために1000個のGoroutineを起動
for i := 0; i < 1000; i++ {
go func() {
// 無限ループでCPUに負荷をかけ続ける
for {
time.Sleep(10 time.Millisecond)
}
}()
}
// 10秒間観察する
fmt.Println("プログラム実行中... GODEBUG=schedtrace=1000 を付けて実行してみてください")
time.Sleep(10 time.Second)
}
応用・注意点
本番環境での利用には注意が必要です。schedtraceは非常に詳細な情報を出力するため、ログが大量に溢れ出し、ディスク容量を圧迫したり、I/O負荷がかえってパフォーマンスを低下させたりする可能性があります。基本的には、ローカル環境や検証環境でのボトルネック調査に使用しましょう。
また、もし「runq(実行待ちキュー)」の値が常に高い場合、Goroutineの数が多すぎるか、重い処理がブロックされている可能性が高いです。その場合は、pprofなどのプロファイリングツールと組み合わせて、どの関数がCPU時間を浪費しているのかを特定するのが、現場での定石的なトラブルシューティング手法となります。

コメント