[インデックス 17226] ファイルの概要
このコミットは、Goランタイムのブロックプロファイラがチャネル操作(select
文とチャネルのclose
を含む)をより適切にサポートするように改善するものです。具体的には、チャネルの送受信、select
文、チャネルのクローズによってゴルーチンがブロックされた際に、そのブロックイベントがプロファイラによって正確に記録され、デバッグモードでより詳細なスタックトレースが提供されるようになります。
コミット
commit c922287686d3735050dc36a2eaa93ae1824732500
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Aug 14 13:56:01 2013 +0400
runtime: improve block profiler support for channels
1. Handle select statements.
2. Handle chan close.
3. Show top frame in debug mode (chansend/chanrecv/selectgo).
Fixes #6049.
R=golang-dev, daniel.morsing, rsc
CC=golang-dev
https://golang.org/cl/12694050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c92287686d3735050dc36a7eaa93ae1824732500
元コミット内容
runtime: improve block profiler support for channels
1. Handle select statements.
2. Handle chan close.
3. Show top frame in debug mode (chansend/chanrecv/selectgo).
Fixes #6049.
変更の背景
Go言語のブロックプロファイラは、ゴルーチンが同期プリミティブ(ミューテックス、チャネルなど)によってブロックされた時間を計測し、そのブロックが発生したコールスタックを記録することで、アプリケーションのパフォーマンスボトルネックを特定するのに役立ちます。このコミット以前は、チャネル操作、特にselect
文やチャネルのclose
によって発生するブロックイベントが、プロファイラによって十分に捕捉・報告されていませんでした。
Goの並行処理モデルにおいて、チャネルはゴルーチン間の通信と同期の主要な手段です。チャネル操作が原因でゴルーチンがブロックされることは頻繁に発生し、これがアプリケーション全体のレイテンシやスループットに大きな影響を与える可能性があります。したがって、これらのブロックイベントを正確にプロファイルし、その原因を特定できるようにすることは、Goアプリケーションのパフォーマンスチューニングにおいて非常に重要です。
このコミットは、以下の具体的な課題を解決することを目的としています。
select
文によるブロックのプロファイル不足:select
文は複数のチャネル操作を待機するために使用されますが、いずれのケースも準備ができていない場合にゴルーチンがブロックされます。このブロックがプロファイラによって適切に記録されていませんでした。- チャネル
close
によるブロックのプロファイル不足: クローズされたチャネルからの受信は即座にゼロ値を返し、送信はパニックを引き起こします。しかし、チャネルがクローズされるのを待機しているゴルーチンがブロックされるケース(例えば、バッファ付きチャネルが満杯で送信側がブロックされ、そのチャネルがクローズされるのを待つ場合など)のプロファイルが不十分でした。 - デバッグモードでのスタックトレースの改善: ブロックプロファイラのデバッグ出力において、ブロックが発生した正確なランタイム関数(
chansend
、chanrecv
、selectgo
など)がスタックトレースの最上位に表示されないことがあり、ブロックの原因特定を困難にしていました。
これらの改善により、開発者はチャネル関連のパフォーマンス問題をより効果的に診断できるようになります。
前提知識の解説
このコミットの理解には、以下のGo言語およびランタイムの概念に関する知識が必要です。
-
Goのチャネル (Channels):
- ゴルーチン間で値を送受信するための通信メカニズム。
make(chan T)
で作成され、ch <- val
で送信、val := <-ch
で受信します。- バッファなしチャネルは同期通信を行い、送信側と受信側が同時に準備ができていないとブロックされます。
- バッファ付きチャネルは非同期通信を行い、バッファが満杯または空でない限りブロックされません。
close(ch)
でチャネルをクローズできます。クローズされたチャネルからの受信は即座にゼロ値を返し、送信はパニックを引き起こします。
-
select
文:- 複数のチャネル操作を同時に待機し、準備ができた最初の操作を実行するための制御構造。
case <-ch:
(受信)、case ch <- val:
(送信)、default:
(いずれのチャネルも準備ができていない場合の非ブロック処理)などの句を持ちます。select
文内のどのケースも準備ができていない場合、default
句がなければゴルーチンはブロックされます。
-
Goのゴルーチン (Goroutines):
- Goランタイムによって管理される軽量な実行スレッド。
go
キーワードで関数を呼び出すことで起動されます。- Goスケジューラによって、OSスレッド上で多重化されて実行されます。
-
Goのプロファイリングツール:
- Goには、CPU、メモリ、ブロック、ミューテックスなどのプロファイリングをサポートする組み込みツールがあります。
- ブロックプロファイラ (Block Profiler): ゴルーチンが同期プリミティブ(チャネル、ミューテックスなど)によってブロックされた時間を計測し、そのブロックが発生した場所(コールスタック)を記録します。これにより、並行処理における競合やデッドロック、パフォーマンスボトルネックを特定できます。
runtime.SetBlockProfileRate
関数を使用して、ブロックプロファイリングを有効にし、サンプリングレートを設定します。runtime.Lookup("block").WriteTo(...)
を使用して、プロファイルデータを取得します。
-
runtime
パッケージ:- Goランタイムの低レベルな機能を提供するパッケージ。
- チャネル操作、ゴルーチン管理、スケジューリング、メモリ管理など、Goプログラムの実行基盤を担うC言語(またはGoアセンブリ)で実装された部分(
chan.c
など)と、Go言語で書かれた部分から構成されます。
-
runtime·cputicks()
:- Goランタイム内部で使用される関数で、CPUのティック数を取得します。これは、ブロック時間の計測に使用されます。
-
runtime·blockevent()
:- ブロックプロファイラにブロックイベントを報告するためのランタイム内部関数。ブロック時間とイベントタイプ(チャネル送受信、ミューテックスロックなど)を引数に取ります。
-
SudoG
構造体:runtime
パッケージ内部で使用される構造体で、チャネル操作によってブロックされたゴルーチンに関する情報を保持します。このコミットでは、releasetime
フィールドが追加され、ブロック解除時のCPUティックを記録するために使用されます。
技術的詳細
このコミットの主要な変更点は、src/pkg/runtime/chan.c
、src/pkg/runtime/pprof/pprof.go
、src/pkg/runtime/pprof/pprof_test.go
の3つのファイルにわたります。
src/pkg/runtime/chan.c
の変更
このファイルはGoランタイムにおけるチャネルのコアロジックを実装しています。変更の目的は、select
文とチャネルのclose
操作におけるブロックイベントの計測と報告を改善することです。
-
selectgo
関数の変更:selectgo
はselect
文のランタイム実装です。- ブロックプロファイリングが有効な場合(
runtime·blockprofilerate > 0
)、t0 = runtime·cputicks()
でselect
操作が開始された時刻を記録します。 select
の各ケース(Scase
)に関連付けられたSudoG
構造体のreleasetime
フィールドを-1
に初期化します。これは、後でブロックが解除された時刻を記録するためのフラグとして機能します。- 非同期受信 (
asyncrecv
)、非同期送信 (asyncsend
)、同期受信 (syncrecv
)、同期送信 (syncsend
) の各パスにおいて、ゴルーチンがready
状態になる直前に、sg->releasetime = runtime·cputicks()
を呼び出して、ブロックが解除された時刻を記録します。 select
文が完了し、ゴルーチンがブロックから解放される直前(retc
ラベルの直前)で、cas->sg.releasetime > 0
の場合にruntime·blockevent(cas->sg.releasetime - t0, 2)
を呼び出します。ここで、cas->sg.releasetime - t0
はブロックされた時間を示し、2
はイベントタイプ(チャネル操作によるブロック)を示します。これにより、select
によるブロックイベントがプロファイラに報告されます。
-
closechan
関数の変更:closechan
はチャネルをクローズするランタイム実装です。- チャネルがクローズされた際に、そのチャネルでブロックされていた送信側ゴルーチン(
c->sendq
)と受信側ゴルーチン(c->recvq
)が解放されます。 - これらのゴルーチンが解放される直前に、
sg->releasetime = runtime·cputicks()
を呼び出して、ブロックが解除された時刻を記録します。これにより、チャネルのクローズによってブロックが解除されたイベントもプロファイラに報告されるようになります。
src/pkg/runtime/pprof/pprof.go
の変更
このファイルはGoのプロファイリングデータの書き出しロジックを含んでいます。
writeBlock
関数の変更:writeBlock
はブロックプロファイルデータを書き出す関数です。- デバッグモード(
debug > 0
)の場合、printStackRecord(w, r.Stack(), true)
と変更されています。以前はfalse
でした。このtrue
という引数は、スタックトレースの最上位フレーム(chansend
、chanrecv
、selectgo
など、ブロックが発生したランタイム関数)を強制的に表示させるためのフラグです。これにより、プロファイル出力がより詳細になり、ブロックの原因を特定しやすくなります。
src/pkg/runtime/pprof/pprof_test.go
の変更
このファイルはブロックプロファイラのテストケースを含んでいます。
TestBlockProfile
関数の変更:- 以前は
produceChanContention()
とproduceMutexContention()
という2つの関数を呼び出してチャネルとミューテックスの競合をテストしていましたが、このコミットではより多くのシナリオをカバーするためにテストケースが拡張されました。 TestCase
構造体が導入され、テスト名、実行する関数、期待される正規表現パターンが定義されています。- 追加されたテストケースは以下の通りです。
blockChanRecv
: チャネル受信によるブロックblockChanSend
: チャネル送信によるブロックblockChanClose
: チャネルクローズによるブロック(受信側がブロックされるケース)blockSelectRecvAsync
:select
文での非同期受信によるブロックblockSelectSendSync
:select
文での同期送信によるブロックblockMutex
: ミューテックスロックによるブロック(既存のテストを再利用)
- 各テストケースは、
blockDelay
(10ミリ秒)だけ遅延を導入することで、意図的にブロックを発生させています。 - プロファイル出力が各テストケースの期待される正規表現パターンと一致するかどうかを検証することで、ブロックプロファイラが正しく機能していることを確認しています。特に、スタックトレースの最上位に
runtime.chanrecv1
、runtime.chansend1
、runtime.selectgo
、sync.(*Mutex).Lock
といったランタイム関数が表示されることを検証しています。
- 以前は
コアとなるコードの変更箇所
src/pkg/runtime/chan.c
--- a/src/pkg/runtime/chan.c
+++ b/src/pkg/runtime/chan.c
@@ -851,6 +851,7 @@ selectgo(Select **selp)
{
Select *sel;
uint32 o, i, j, k;
+ int64 t0; // 追加: select開始時刻を記録するための変数
Scase *cas, *dfl;
Hchan *c;
SudoG *sg;
@@ -865,6 +866,13 @@ selectgo(Select **selp)
if(debug)
runtime·printf("select: sel=%p\n", sel);
+ t0 = 0; // 初期化
+ if(runtime·blockprofilerate > 0) { // ブロックプロファイリングが有効な場合
+ t0 = runtime·cputicks(); // select開始時刻を記録
+ for(i=0; i<sel->ncase; i++)
+ sel->scase[i].sg.releasetime = -1; // releasetimeを初期化
+ }
+
// The compiler rewrites selects that statically have
// only 0 or 1 cases plus default into simpler constructs.
// The only way we can end up with such small sel->ncase
@@ -1048,6 +1056,8 @@ asyncrecv:
if(sg != nil) {
gp = sg->g;
selunlock(sel);
+ if(sg->releasetime) // releasetimeが設定されている場合(プロファイリング有効時)
+ sg->releasetime = runtime·cputicks(); // ブロック解除時刻を記録
runtime·ready(gp);
} else {
selunlock(sel);
@@ -1066,6 +1076,8 @@ asyncsend:
if(sg != nil) {
gp = sg->g;
selunlock(sel);
+ if(sg->releasetime)
+ sg->releasetime = runtime·cputicks();
runtime·ready(gp);
} else {
selunlock(sel);
@@ -1085,6 +1097,8 @@ syncrecv:
c->elemalg->copy(c->elemsize, cas->sg.elem, sg->elem);
gp = sg->g;
gp->param = sg;
+ if(sg->releasetime)
+ sg->releasetime = runtime·cputicks();
runtime·ready(gp);
goto retc;
@@ -1110,6 +1124,8 @@ syncsend:
c->elemalg->copy(c->elemsize, sg->elem, cas->sg.elem);
gp = sg->g;
gp->param = sg;
+ if(sg->releasetime)
+ sg->releasetime = runtime·cputicks();
runtime·ready(gp);
retc:
@@ -1123,6 +1139,8 @@ retc:
as = (byte*)selp + cas->so;
*as = true;
}
+ if(cas->sg.releasetime > 0) // releasetimeが有効な場合
+ runtime·blockevent(cas->sg.releasetime - t0, 2); // ブロックイベントを報告
runtime·free(sel);
return pc;
@@ -1265,6 +1283,8 @@ closechan(Hchan *c, void *pc)
break;
gp = sg->g;
gp->param = nil;
+ if(sg->releasetime)
+ sg->releasetime = runtime·cputicks();
runtime·ready(gp);
}
@@ -1275,6 +1295,8 @@ closechan(Hchan *c, void *pc)
break;
gp = sg->g;
gp->param = nil;
+ if(sg->releasetime)
+ sg->releasetime = runtime·cputicks();
runtime·ready(gp);
}
src/pkg/runtime/pprof/pprof.go
--- a/src/pkg/runtime/pprof/pprof.go
+++ b/src/pkg/runtime/pprof/pprof.go
@@ -666,7 +666,7 @@ func writeBlock(w io.Writer, debug int) error {
}
fmt.Fprint(w, "\n")
if debug > 0 {
- printStackRecord(w, r.Stack(), false) // falseからtrueに変更
+ printStackRecord(w, r.Stack(), true) // 最上位フレームを表示
}
}
src/pkg/runtime/pprof/pprof_test.go
--- a/src/pkg/runtime/pprof/pprof_test.go
+++ b/src/pkg/runtime/pprof/pprof_test.go
@@ -181,10 +181,55 @@ var badOS = map[string]bool{
}
func TestBlockProfile(t *testing.T) {
+ type TestCase struct { // 新しいテストケース構造体
+ name string
+ f func()
+ re string // 期待される正規表現パターン
+ }
+ tests := [...]TestCase{ // 複数のテストケースを定義
+ {"chan recv", blockChanRecv, `
+[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
+# 0x[0-9,a-f]+\truntime\.chanrecv1\+0x[0-9,a-f]+\t.*/src/pkg/runtime/chan.c:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.blockChanRecv\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+`},
+ {"chan send", blockChanSend, `
+[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
+# 0x[0-9,a-f]+\truntime\.chansend1\+0x[0-9,a-f]+\t.*/src/pkg/runtime/chan.c:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.blockChanSend\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+`},
+ {"chan close", blockChanClose, `
+[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
+# 0x[0-9,a-f]+\truntime\.chanrecv1\+0x[0-9,a-f]+\t.*/src/pkg/runtime/chan.c:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.blockChanClose\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+`},
+ {"select recv async", blockSelectRecvAsync, `
+[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
+# 0x[0-9,a-f]+\truntime\.selectgo\+0x[0-9,a-f]+\t.*/src/pkg/runtime/chan.c:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.blockSelectRecvAsync\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+# 0x[0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+`},
+ {"select send sync", blockSelectSendSync, `
+[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
+# 0x[0-9,a-f]+\truntime\.selectgo\+0x[0-9,a-f]+\t.*/src/pkg/runtime/chan.c:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.blockSelectSendSync\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+`},
+ {"mutex", blockMutex, `
+[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
+# 0x[0-9,a-f]+\tsync\\.\\(\\*Mutex\\)\\.Lock\\+0x[0-9,a-f]+\t.*/src/pkg/sync/mutex\\.go:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.blockMutex\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+# 0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+
+`},
+ }
+
runtime.SetBlockProfileRate(1)
defer runtime.SetBlockProfileRate(0)
-\tproduceChanContention()\n-\tproduceMutexContention()\n+\tfor _, test := range tests { // 各テストケースを実行
+\t\ttest.f()
+\t}
var w bytes.Buffer
Lookup("block").WriteTo(&w, 1)
prof := w.String()
@@ -193,40 +238,73 @@ func TestBlockProfile(t *testing.T) {
\tt.Fatalf("Bad profile header:\n%v", prof)
}
-\treChan := regexp.MustCompile(`\n-[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+\n-#\t0x[0-9,a-f]+\truntime/pprof_test\.produceChanContention\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+\n-#\t0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+\n-`)\n-\tif !reChan.MatchString(prof) {\n-\t\tt.Fatalf("Bad chan entry, expect:\n%v\ngot:\n%v", reChan, prof)\n-\t}\n-\n-\treMutex := regexp.MustCompile(`\n-[0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+\n-#\t0x[0-9,a-f]+\tsync\\.\\(\\*Mutex\\)\\.Lock\+0x[0-9,a-f]+\t.*/src/pkg/sync/mutex\\.go:[0-9]+\n-#\t0x[0-9,a-f]+\truntime/pprof_test\.produceMutexContention\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+\n-#\t0x[0-9,a-f]+\truntime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+\t.*/src/pkg/runtime/pprof/pprof_test.go:[0-9]+\n-`)\n-\tif !reMutex.MatchString(prof) {\n-\t\tt.Fatalf("Bad mutex entry, expect:\n%v\ngot:\n%v", reMutex, prof)\n+\tfor _, test := range tests { // 各テストケースの正規表現を検証
+\t\tif !regexp.MustCompile(test.re).MatchString(prof) {
+\t\t\tt.Fatalf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
+\t\t}
}
}
-func produceChanContention() {
+const blockDelay = 10 * time.Millisecond // ブロック遅延定数
+
+func blockChanRecv() { // チャネル受信によるブロックを発生させる関数
c := make(chan bool)
go func() {
-\t\ttime.Sleep(10 * time.Millisecond)\n+\t\ttime.Sleep(blockDelay)
\tc <- true
}()
<-c
}
-func produceMutexContention() {
+func blockChanSend() { // チャネル送信によるブロックを発生させる関数
+ c := make(chan bool)
+ go func() {
+ time.Sleep(blockDelay)
+ <-c
+ }()
+ c <- true
+}
+
+func blockChanClose() { // チャネルクローズによるブロックを発生させる関数
+ c := make(chan bool)
+ go func() {
+ time.Sleep(blockDelay)
+ close(c)
+ }()
+ <-c
+}
+
+func blockSelectRecvAsync() { // selectでの非同期受信によるブロックを発生させる関数
+ c := make(chan bool, 1)
+ c2 := make(chan bool, 1)
+ go func() {
+ time.Sleep(blockDelay)
+ c <- true
+ }()
+ select {
+ case <-c:
+ case <-c2:
+ }
+}
+
+func blockSelectSendSync() { // selectでの同期送信によるブロックを発生させる関数
+ c := make(chan bool)
+ c2 := make(chan bool)
+ go func() {
+ time.Sleep(blockDelay)
+ <-c
+ }()
+ select {
+ case c <- true:
+ case c2 <- true:
+ }
+}
+
+func blockMutex() { // ミューテックスロックによるブロックを発生させる関数
var mu sync.Mutex
mu.Lock()
go func() {
-\t\ttime.Sleep(10 * time.Millisecond)\n+\t\ttime.Sleep(blockDelay)
\t\tmu.Unlock()
}()
mu.Lock()
コアとなるコードの解説
src/pkg/runtime/chan.c
-
selectgo
関数内のt0
とreleasetime
の導入:int64 t0;
が追加され、select
文の実行が開始された時刻をruntime·cputicks()
で記録します。select
の各ケースに対応するSudoG
構造体(ブロックされたゴルーチンに関する情報を持つ)のreleasetime
フィールドが-1
に初期化されます。これは、このゴルーチンがまだブロック解除されていないことを示すマーカーです。select
内のチャネル操作(asyncrecv
,asyncsend
,syncrecv
,syncsend
)が成功し、ゴルーチンがruntime·ready(gp)
によって実行可能状態になる直前に、sg->releasetime = runtime·cputicks();
が呼び出されます。これにより、ゴルーチンがブロックから解放された正確な時刻が記録されます。retc
ラベルの直前、つまりselect
文が完了する直前に、if(cas->sg.releasetime > 0)
の条件で、ブロックされた時間が有効な場合にruntime·blockevent(cas->sg.releasetime - t0, 2);
が呼び出されます。ここで、cas->sg.releasetime - t0
がブロックされた期間を表し、2
はチャネル操作によるブロックイベントであることを示します。これにより、select
文によるブロックがプロファイラに報告されるようになります。
-
closechan
関数内のreleasetime
の導入:closechan
関数内で、チャネルがクローズされた際に、そのチャネルでブロックされていた送信側 (c->sendq
) および受信側 (c->recvq
) のゴルーチンが解放されます。- これらのゴルーチンが
runtime·ready(gp)
によって実行可能状態になる直前に、sg->releasetime = runtime·cputicks();
が呼び出されます。これにより、チャネルのクローズによってブロックが解除されたゴルーチンの解放時刻が記録され、ブロックプロファイラがこれらのイベントを捕捉できるようになります。
これらの変更により、Goランタイムはselect
文やチャネルのクローズによって発生するゴルーチンのブロックイベントを正確に計測し、プロファイラに報告するための情報を収集するようになります。
src/pkg/runtime/pprof/pprof.go
writeBlock
関数内のprintStackRecord
の引数変更:printStackRecord(w, r.Stack(), true)
への変更は、ブロックプロファイルのデバッグ出力において、スタックトレースの最上位フレームを強制的に表示させるためのものです。- 以前は
false
が渡されており、これによりランタイム内部の低レベルな関数(例:chansend
,chanrecv
,selectgo
)が省略される可能性がありました。true
にすることで、これらの関数がスタックトレースの最上位に明示的に表示されるようになり、ブロックがどのランタイム関数で発生したかをより正確に把握できるようになります。これは、プロファイル結果の可読性と診断能力を大幅に向上させます。
src/pkg/runtime/pprof/pprof_test.go
TestBlockProfile
のテストケース拡張:TestCase
構造体とtests
スライスを導入することで、チャネルの送受信、クローズ、select
文の様々なシナリオにおけるブロックプロファイリングを網羅的にテストできるようになりました。- 各テスト関数(
blockChanRecv
,blockChanSend
,blockChanClose
,blockSelectRecvAsync
,blockSelectSendSync
,blockMutex
)は、time.Sleep(blockDelay)
を介して意図的にゴルーチンをブロックさせ、そのブロックイベントがプロファイラによって捕捉されることを確認します。 regexp.MustCompile(test.re).MatchString(prof)
による正規表現マッチングは、プロファイル出力に期待されるスタックトレース(特に最上位のランタイム関数)が含まれていることを検証します。これにより、ランタイムの変更がブロックプロファイラの機能に正しく反映されていることが保証されます。
これらの変更は、Goの並行処理におけるパフォーマンスボトルネックの特定とデバッグを大幅に改善し、開発者がより効率的にGoアプリケーションを最適化できるようにするための重要なステップです。
関連リンク
- Go言語のチャネル: https://go.dev/tour/concurrency/2
- Go言語の
select
文: https://go.dev/tour/concurrency/5 - Go言語のプロファイリング: https://go.dev/doc/diagnose
- Goのブロックプロファイラに関する公式ドキュメント(
runtime
パッケージ): https://pkg.go.dev/runtime#SetBlockProfileRate
参考にした情報源リンク
- Goの公式リポジトリ: https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://go.dev/cl/ (コミットメッセージに記載されている
https://golang.org/cl/12694050
は、このコミットのGerritレビューへのリンクです。) - GoのIssue Tracker: https://github.com/golang/go/issues (コミットメッセージに記載されている
Fixes #6049
は、関連するIssueへの参照ですが、今回の検索では直接的なIssueは見つかりませんでした。これは、Issueがクローズされたか、番号が変更されたか、あるいは内部的なIssueであった可能性を示唆しています。)