Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 19622] ファイルの概要

このコミットは、Goランタイムのスケジューラトレースの出力を改善するものです。具体的には、スケジューラが定常状態にあるか否かを理解するのに役立つ「スピン中のスレッド数(spinning threads)」をトレース情報に追加します。

コミット

  • コミットハッシュ: a7186dc3035dffef8ac9033966da52e4cc935ef0
  • Author: Dmitriy Vyukov dvyukov@google.com
  • Date: Thu Jun 26 17:16:43 2014 -0700

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a7186dc3035dffef8ac9033966da52e4cc935ef0

元コミット内容

runtime: improve scheduler trace
Output number of spinning threads,
this is useful to understanding whether the scheduler
is in a steady state or not.

R=golang-codereviews, khr
CC=golang-codereviews, rsc
https://golang.org/cl/103540045

変更の背景

Goランタイムのスケジューラは、ゴルーチン(G)、論理プロセッサ(P)、OSスレッド(M)からなるG-M-Pモデルに基づいて動作します。このスケジューラは、プログラムの並行性を効率的に管理し、CPUリソースを最大限に活用するために非常に重要です。

スケジューラの動作をデバッグしたり、パフォーマンスを分析したりする際には、その内部状態を正確に把握することが不可欠です。特に、スレッドが「スピンしている」状態は、スケジューラが新しい作業を探している、あるいはブロッキング操作から復帰したゴルーチンを待っているなど、特定の状況下で発生します。スピン中のスレッド数が多い場合、それはスケジューラが効率的にゴルーチンをディスパッチできていない、あるいはシステムに過剰な競合がある可能性を示唆することがあります。

このコミットの目的は、runtime·schedtrace 関数が出力する情報に「スピン中のスレッド数」を追加することで、スケジューラの状態、特にそれが「定常状態(steady state)」にあるのか、それとも何らかのボトルネックや非効率な状態にあるのかをより正確に判断できるようにすることです。これにより、開発者や運用者は、Goアプリケーションのパフォーマンス特性をより深く理解し、潜在的な問題を特定できるようになります。

前提知識の解説

GoスケジューラのG-M-Pモデル

Goランタイムのスケジューラは、以下の3つの主要な要素で構成されるG-M-Pモデルを採用しています。

  1. G (Goroutine): Goにおける軽量な並行実行単位です。OSスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行できます。Goランタイムが管理し、必要に応じてスタックサイズを動的に増減させます。
  2. M (Machine): オペレーティングシステム(OS)のスレッドを表します。Goランタイムは、複数のゴルーチンを少数のOSスレッドに多重化して実行します。MはCPU上で実際にコードを実行するエンティティです。
  3. P (Processor): 論理プロセッサを表し、ゴルーチンを実行するためのコンテキストを提供します。MはPと関連付けられて初めてゴルーチンを実行できます。各Pはローカルな実行キューを持ち、そこに実行可能なゴルーチンが格納されます。runtime.GOMAXPROCS 環境変数または関数によってPの数が制御され、通常はCPUコア数に設定されます。

スケジューラは、GをPのローカルキューに配置し、MがPからGを取り出して実行することで、効率的な並行処理を実現します。Pのローカルキューが空になった場合、Mは他のPからゴルーチンを「盗む(work stealing)」ことで、CPUの利用率を最大化しようとします。

スピン中のスレッド(Spinning Threads)

「スピン中のスレッド」とは、Goランタイムにおいて、OSスレッド(M)が新しい作業(実行可能なゴルーチン)を探している状態、またはブロッキング操作から復帰したゴルーチンを待っている状態を指します。

通常、MはPと関連付けられてゴルーチンを実行しますが、ゴルーチンがシステムコールなどのブロッキング操作を実行すると、そのMはPから切り離され、ブロッキング操作を処理します。このとき、Pはすぐに別のM(既存のアイドルなMか、新しく作成されたM)に引き継がれ、他のゴルーチンの実行を継続できます。

スピン中のMは、すぐに自身をパーク(スリープ状態に移行)するのではなく、短期間「スピン」して繰り返し新しい作業がないかを確認します。これは、コンテキストスイッチのオーバーヘッドを削減するための最適化です。もしすぐに新しい作業が利用可能になった場合、スピン中のMは高コストなOSによるスケジューリングプロセスを経ることなく、その作業をピックアップできます。

ただし、スピン中のスレッドが多すぎると、CPUリソースを無駄に消費する可能性があります。そのため、Goランタイムはスピン中のMの数を制限しています。nmspinning は、このスピン中のMの数を追跡するランタイム内部のカウンタです。

src/pkg/runtime/proc.c

src/pkg/runtime/proc.c は、Goランタイムのスケジューラの中核部分を実装しているC言語のファイルです。ゴルーチンのスケジューリング、OSスレッドの管理、プロセッサ(P)の割り当てなど、Goの並行処理モデルの基盤となるロジックが含まれています。runtime·schedtrace 関数もこのファイル内に定義されており、スケジューラのデバッグ情報を提供します。

技術的詳細

このコミットは、Goランタイムのスケジューラトレース機能である runtime·schedtrace 関数に変更を加えるものです。この関数は、Goプログラムの実行中にスケジューラの状態に関する情報を出力するために使用されます。

変更の核心は、トレース出力に spinningthreads という新しいメトリクスを追加することです。これは、Goランタイム内部で管理されている runtime·sched.nmspinning という変数から取得されます。nmspinning は、現在スピンしているOSスレッド(M)の数を表します。

以前の runtime·schedtrace の出力は、gomaxprocs, idleprocs, threads, idlethreads, runqueue などの情報を含んでいましたが、スピン中のスレッド数という重要な指標が欠けていました。この情報が追加されることで、スケジューラがアイドル状態にあるのか、それとも作業を探して積極的にスピンしているのかを区別できるようになります。

また、詳細なトレース (detailedtrue の場合) の出力からも nmspinning が削除されています。これは、nmspinning がすでに主要なトレース行に追加されたため、冗長性を避けるための変更です。これにより、出力の重複が解消され、より簡潔なトレース情報が提供されます。

この変更により、Goアプリケーションのパフォーマンスプロファイリングやデバッグにおいて、スケジューラの挙動をより深く、より正確に分析することが可能になります。例えば、spinningthreads の値が継続的に高い場合、それはゴルーチンのブロッキングが多い、あるいはCPUリソースの競合が激しいといった問題を示唆する可能性があります。

コアとなるコードの変更箇所

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -2730,12 +2730,12 @@ runtime·schedtrace(bool detailed)\n \t\tstarttime = now;\n \n \truntime·lock(&runtime·sched);\n-\truntime·printf(\"SCHED %Dms: gomaxprocs=%d idleprocs=%d threads=%d idlethreads=%d runqueue=%d\",\n+\truntime·printf(\"SCHED %Dms: gomaxprocs=%d idleprocs=%d threads=%d spinningthreads=%d idlethreads=%d runqueue=%d\",\n \t\t(now-starttime)/1000000, runtime·gomaxprocs, runtime·sched.npidle, runtime·sched.mcount,\n-\t\truntime·sched.nmidle, runtime·sched.runqsize);\n+\t\truntime·sched.nmspinning, runtime·sched.nmidle, runtime·sched.runqsize);\n \tif(detailed) {\n-\t\truntime·printf(\" gcwaiting=%d nmidlelocked=%d nmspinning=%d stopwait=%d sysmonwait=%d\\n\",\n-\t\t\truntime·sched.gcwaiting, runtime·sched.nmidlelocked, runtime·sched.nmspinning,\n+\t\truntime·printf(\" gcwaiting=%d nmidlelocked=%d stopwait=%d sysmonwait=%d\\n\",\n+\t\t\truntime·sched.gcwaiting, runtime·sched.nmidlelocked,\n \t\t\truntime·sched.stopwait, runtime·sched.sysmonwait);\n \t}\n \t// We must be careful while reading data from P\'s, M\'s and G\'s.\n```

## コアとなるコードの解説

このコミットでは、`src/pkg/runtime/proc.c` ファイル内の `runtime·schedtrace` 関数が変更されています。

1.  **主要なトレース行の変更**:
    変更前:
    ```c
    runtime·printf("SCHED %Dms: gomaxprocs=%d idleprocs=%d threads=%d idlethreads=%d runqueue=%d",
        (now-starttime)/1000000, runtime·gomaxprocs, runtime·sched.npidle, runtime·sched.mcount,
        runtime·sched.nmidle, runtime·sched.runqsize);
    ```
    変更後:
    ```c
    runtime·printf("SCHED %Dms: gomaxprocs=%d idleprocs=%d threads=%d spinningthreads=%d idlethreads=%d runqueue=%d",
        (now-starttime)/1000000, runtime·gomaxprocs, runtime·sched.npidle, runtime·sched.mcount,
        runtime·sched.nmspinning, runtime·sched.nmidle, runtime·sched.runqsize);
    ```
    この変更により、`runtime·printf` のフォーマット文字列に `spinningthreads=%d` が追加され、対応する引数として `runtime·sched.nmspinning` が渡されるようになりました。これにより、スケジューラの概要情報にスピン中のスレッド数が含まれるようになります。`runtime·sched.nmspinning` は、Goランタイムが内部で管理している、現在スピン状態にあるM(OSスレッド)の数を表すフィールドです。

2.  **詳細トレース行の変更**:
    変更前 (`if(detailed)` ブロック内):
    ```c
    runtime·printf(" gcwaiting=%d nmidlelocked=%d nmspinning=%d stopwait=%d sysmonwait=%d\n",
        runtime·sched.gcwaiting, runtime·sched.nmidlelocked, runtime·sched.nmspinning,
        runtime·sched.stopwait, runtime·sched.sysmonwait);
    ```
    変更後 (`if(detailed)` ブロック内):
    ```c
    runtime·printf(" gcwaiting=%d nmidlelocked=%d stopwait=%d sysmonwait=%d\n",
        runtime·sched.gcwaiting, runtime·sched.nmidlelocked,
        runtime·sched.stopwait, runtime·sched.sysmonwait);
    ```
    詳細トレースの出力から `nmspinning=%d` とそれに対応する `runtime·sched.nmspinning` の引数が削除されました。これは、`nmspinning` がすでに主要なトレース行で出力されるようになったため、詳細トレースでの重複を避けるための調整です。これにより、出力がより整理され、必要な情報が適切な場所で提供されるようになります。

これらの変更により、Goランタイムのスケジューラトレースは、スケジューラの動作状態をより包括的に把握するための重要な指標である「スピン中のスレッド数」を提供するようになり、デバッグやパフォーマンス分析の精度が向上します。

## 関連リンク

- Go CL (Code Review) ページ: [https://golang.org/cl/103540045](https://golang.org/cl/103540045)

## 参考にした情報源リンク

- Go scheduler G-M-P model and spinning threads:
    - [https://medium.com/@ankur_anand/go-scheduler-part-1-goroutine-m-p-model-1f3e7c4d2e3a](https://medium.com/@ankur_anand/go-scheduler-part-1-goroutine-m-p-model-1f3e7c4d2e3a)
    - [https://medium.com/@ankur_anand/go-scheduler-part-2-spinning-threads-and-blocking-syscalls-1f3e7c4d2e3a](https://medium.com/@ankur_anand/go-scheduler-part-2-spinning-threads-and-blocking-syscalls-1f3e7c4d2e3a)
    - [https://sobyte.net/post/2022-03/go-scheduler/](https://sobyte.net/post/2022-03/go-scheduler/)
    - [https://dev.to/ardanlabs/the-go-scheduler-part-1-g-m-p-model-3e0](https://dev.to/ardanlabs/the-go-scheduler-part-1-g-m-p-model-3e0)
    - [https://www.ardanlabs.com/blog/2018/08/go-scheduler-part2.html](https://www.ardanlabs.com/blog/2018/08/go-scheduler-part2.html)
    - [https://medium.com/a-journey-with-go/go-goroutine-and-os-thread-1e273469974f](https://medium.com/a-journey-with-go/go-goroutine-and-os-thread-1e273469974f)
    - [https://rohanreddyalleti.com/blog/go-scheduler-internals/](https://rohanreddyalleti.com/blog/go-scheduler-internals/)
    - [https://segmentfault.com/a/1190000040000000](https://segmentfault.com/a/1190000040000000)
    - [https://blogs.sap.com/2023/03/29/understanding-the-go-scheduler-a-deep-dive-into-g-m-p-model/](https://blogs.sap.com/2023/03/29/understanding-the-go-scheduler-a-deep-dive-into-g-m-p-model/)