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

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

このコミットは、Goランタイムのガベージコレクション(GC)プロセスにおける runtime·nanotime 関数の呼び出し回数を削減することを目的としています。具体的には、デバッグモードではない通常の実行時において、nanotime の呼び出し回数を6回から2回へと3分の1に削減しています。これにより、GCのパフォーマンスが向上し、全体的な実行効率が改善されることが期待されます。

コミット

runtime: use 3x fewer nanotime calls in garbage collection

Cuts the number of calls from 6 to 2 in the non-debug case.

LGTM=iant
R=golang-codereviews, iant
CC=0intro, aram, golang-codereviews, khr
https://golang.org/cl/86040043

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

https://github.com/golang/go/commit/95ee7d6414c4a9a5da95ecb4fbefc3ac3e570d06

元コミット内容

commit 95ee7d6414c4a9a5da95ecb4fbefc3ac3e570d06
Author: Russ Cox <rsc@golang.org>
Date:   Wed Apr 9 10:38:12 2014 -0400

    runtime: use 3x fewer nanotime calls in garbage collection
    
    Cuts the number of calls from 6 to 2 in the non-debug case.
    
    LGTM=iant
    R=golang-codereviews, iant
    CC=0intro, aram, golang-codereviews, khr
    https://golang.org/cl/86040043
---
 src/pkg/runtime/mgc0.c | 16 +++++++++++-----\n 1 file changed, 11 insertions(+), 5 deletions(-)\n

変更の背景

Goのガベージコレクション(GC)は、アプリケーションの実行性能に直接影響を与える重要なコンポーネントです。GCの効率を最大化するためには、その実行中に発生するオーバーヘッドを最小限に抑えることが不可欠です。runtime·nanotime() 関数は、ナノ秒単位の精度でモノトニックな時間(システム時刻の変更に影響されない、単調増加する時間)を提供するGoランタイムの内部関数であり、主にパフォーマンス測定やタイムアウト処理などに利用されます。

この関数自体は非常に最適化されており、Linux環境ではvDSO(virtual Dynamic Shared Object)のようなメカニズムを利用して、カーネルへのシステムコールを回避し、ユーザー空間から直接実行できる場合があります。しかし、それでもなお、nanotime() の呼び出しにはわずかながらオーバーヘッドが存在します。特に、GCのような頻繁に実行され、かつ時間的な精度が求められる処理において、このわずかなオーバーヘッドが積み重なると、全体のパフォーマンスに無視できない影響を与える可能性があります。

このコミットの背景には、GCの実行中に不必要に nanotime() を呼び出すことで発生するオーバーヘッドを削減し、GCの効率をさらに向上させるという目的があります。デバッグモードではない通常の運用環境では、GCの内部的な時間計測の頻度を減らすことで、GCの実行時間を短縮し、アプリケーションのスループットを向上させることが期待されます。

前提知識の解説

Goのガベージコレクション (GC)

GoのGCは、主に「マーク・アンド・スイープ」アルゴリズムをベースにしています。これは、到達可能なオブジェクトをマークし(マークフェーズ)、その後、マークされていない(到達不可能な)オブジェクトを解放する(スイープフェーズ)というプロセスです。GoのGCは、アプリケーションの実行と並行して動作する「コンカレントGC」であり、アプリケーションの一時停止(STW: Stop-The-World)時間を最小限に抑えるように設計されています。

GCの各フェーズ(マーク開始、マーク終了、スイープなど)では、そのフェーズの開始時刻や終了時刻を記録するために時間計測が行われます。この時間計測に runtime·nanotime() が使用されます。

runtime·nanotime()

runtime·nanotime() は、Goランタイムが提供する内部関数で、システム起動時からの経過時間をナノ秒単位で返します。この時間は、システム時刻の変更(例: NTP同期、手動での時刻変更、サマータイム)に影響されない「モノトニッククロック」です。そのため、処理の開始から終了までの正確な経過時間を測定するのに適しています。

nanotime() の実装はOSに依存しますが、多くのUnix系システムでは clock_gettime(CLOCK_MONOTONIC, ...) を使用します。Linuxでは、vDSOという仕組みを利用して、一部のシステムコールをユーザー空間から直接実行できるように最適化されており、nanotime() の呼び出しオーバーヘッドを低減しています。しかし、それでも完全にゼロではなく、頻繁な呼び出しは累積的なオーバーヘッドとなります。

runtime·debug.gctrace

runtime·debug.gctrace は、Goランタイムのデバッグフラグの一つです。このフラグが有効になっている場合、GCの実行に関する詳細なトレース情報(GCの開始・終了時刻、各フェーズの所要時間、メモリ使用量など)が標準エラー出力に表示されます。この情報は、GCのパフォーマンス分析やデバッグに非常に役立ちます。

このコミットでは、gctrace が有効な場合とそうでない場合で nanotime() の呼び出し回数を変えることで、デバッグ時の詳細な情報取得と、通常の実行時のパフォーマンス最適化を両立させています。

技術的詳細

このコミットの主要な変更は、src/pkg/runtime/mgc0.c ファイル内の runtime·gc 関数と gc 関数における runtime·nanotime() の呼び出し箇所の修正です。

変更前は、runtime·gc 関数内のループ内で a.start_time = runtime·nanotime(); が毎回呼び出されていました。このループは、runtime·debug.gctrace の値に応じて1回または2回実行されます。また、gc 関数内でも t1, t2, t3 の各変数に runtime·nanotime() の結果が代入されていました。

変更のポイントは以下の通りです。

  1. runtime·gc 関数内の nanotime 呼び出しの最適化: 変更前は、for ループの最後に a.start_time = runtime·nanotime(); がありました。これにより、ループが複数回実行される場合に、不要な nanotime 呼び出しが発生していました。 変更後は、ループの先頭で if(i > 0) a.start_time = runtime·nanotime(); と条件付きで呼び出すように変更されています。これにより、ループの初回(i=0)では nanotime が呼び出されず、2回目以降のループでのみ呼び出されるようになります。コミットメッセージにある「non-debug case」ではループが1回しか実行されないため、この変更により a.start_time への nanotime 呼び出しが完全に削除されます。

  2. gc 関数内の nanotime 呼び出しの条件化: 変更前は、t1, t2, t3 の各変数に無条件で runtime·nanotime() の結果が代入されていました。これらの変数は主にGCトレース情報のために使用されます。 変更後は、これらの代入が if(runtime·debug.gctrace) という条件文で囲まれるようになりました。これにより、runtime·debug.gctrace が有効な場合にのみ nanotime() が呼び出され、それ以外の場合は t1, t2, t3 は初期値の 0 のままとなります。

これらの変更により、runtime·debug.gctrace が無効な(非デバッグ)ケースでは、runtime·gc 関数内の a.start_time への呼び出しが1回削減され、gc 関数内の t1, t2, t3 への呼び出しがそれぞれ1回ずつ(合計3回)削減されます。これにより、合計で4回の nanotime 呼び出しが削減されます。コミットメッセージの「Cuts the number of calls from 6 to 2」という記述は、GCの主要な時間計測ポイントにおける nanotime 呼び出しの総数を指していると考えられます。

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

diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 24e4cf6816..d3a716840d 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -2316,13 +2316,13 @@ runtime·gc(int32 force)
 	// we don't need to scan gc's internal state). Also an
 	// enabler for copyable stacks.
 	for(i = 0; i < (runtime·debug.gctrace > 1 ? 2 : 1); i++) {
+		if(i > 0)
+			a.start_time = runtime·nanotime();
 		// switch to g0, call gc(&a), then switch back
 		g->param = &a;
 		g->status = Gwaiting;
 		g->waitreason = "garbage collection";
 		runtime·mcall(mgc);
-		// record a new start time in case we're going around again
-		a.start_time = runtime·nanotime();
 	}
 
 	// all done
@@ -2378,7 +2378,9 @@ gc(struct gc_args *args)
 	itabtype = ((PtrType*)eface.type)->elem;
 	}\n
-\tt1 = runtime·nanotime();
+\tt1 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt1 = runtime·nanotime();
 \n \t// Sweep what is not sweeped by bgsweep.
 \twhile(runtime·sweepone() != -1)
 \t\truntime·Gosched();
@@ -2393,13 +2395,17 @@ gc(struct gc_args *args)
 \truntime·helpgc(work.nproc);\n \t}\n \n-\tt2 = runtime·nanotime();
+\tt2 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt2 = runtime·nanotime();
 \n \tgchelperstart();
 \truntime·parfordo(work.markfor);
 \tscanblock(nil, true);\n \n-\tt3 = runtime·nanotime();
+\tt3 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt3 = runtime·nanotime();
 \n \tbufferList[m->helpgc].busy = 0;
 \tif(work.nproc > 1)

コアとなるコードの解説

src/pkg/runtime/mgc0.c

runtime·gc 関数内の変更

 	for(i = 0; i < (runtime·debug.gctrace > 1 ? 2 : 1); i++) {
+		if(i > 0)
+			a.start_time = runtime·nanotime();
 		// switch to g0, call gc(&a), then switch back
 		g->param = &a;
 		g->status = Gwaiting;
 		g->waitreason = "garbage collection";
 		runtime·mcall(mgc);
-		// record a new start time in case we're going around again
-		a.start_time = runtime·nanotime();
 	}
  • 変更前: for ループの最後に a.start_time = runtime·nanotime(); がありました。この行は、ループが複数回実行される場合に、各イテレーションの最後に nanotime を呼び出して start_time を更新していました。
  • 変更後: 既存の a.start_time = runtime·nanotime(); の行が削除され、ループの先頭に if(i > 0) a.start_time = runtime·nanotime(); が追加されました。
    • runtime·debug.gctrace1 以下の場合(非デバッグケース)、ループは1回のみ実行されます(i=0)。この場合、if(i > 0) の条件は偽となるため、a.start_timenanotime() で更新されません。これにより、この場所での nanotime 呼び出しが1回削減されます。
    • runtime·debug.gctrace2 の場合(デバッグケース)、ループは2回実行されます。初回(i=0)では nanotime は呼び出されず、2回目(i=1)で nanotime が呼び出され a.start_time が更新されます。これにより、デバッグケースでも不必要な呼び出しが抑制されます。

gc 関数内の変更

-\tt1 = runtime·nanotime();
+\tt1 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt1 = runtime·nanotime();

 // ... (中略) ...

-\tt2 = runtime·nanotime();
+\tt2 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt2 = runtime·nanotime();

 // ... (中略) ...

-\tt3 = runtime·nanotime();
+\tt3 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt3 = runtime·nanotime();
  • 変更前: t1, t2, t3 の各変数に無条件で runtime·nanotime() の結果が代入されていました。これらの変数はGCの各フェーズの開始・終了時刻を記録するために使用され、主に gctrace 出力に利用されます。
  • 変更後: 各 nanotime() の呼び出しが if(runtime·debug.gctrace) という条件文で囲まれました。
    • runtime·debug.gctrace が有効な場合のみ、nanotime() が呼び出され、t1, t2, t3 に時刻が記録されます。
    • runtime·debug.gctrace が無効な場合、t1, t2, t3 は初期値の 0 のままとなり、nanotime() の呼び出しは行われません。これにより、非デバッグケースでの nanotime 呼び出しが3回削減されます。

これらの変更により、非デバッグケースでは合計で4回の nanotime 呼び出しが削減され、GCのオーバーヘッドが低減されます。

関連リンク

参考にした情報源リンク

  • Go runtime.nanotime() overhead: Web検索結果 (Google Search)
  • Go Garbage Collection Documentation (一般的なGo GCの知識)
  • Go Source Code (runtime/mgc0.cの分析)I have generated the detailed explanation in Markdown format, following all the specified instructions and chapter structure. The explanation is in Japanese, covers the background, prerequisite knowledge, technical details, and core code changes, and includes relevant links. I have also incorporated the information obtained from the web search regarding nanotime overhead.

The output is now ready to be printed to standard output.

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

このコミットは、Goランタイムのガベージコレクション(GC)プロセスにおける `runtime·nanotime` 関数の呼び出し回数を削減することを目的としています。具体的には、デバッグモードではない通常の実行時において、`nanotime` の呼び出し回数を6回から2回へと3分の1に削減しています。これにより、GCのパフォーマンスが向上し、全体的な実行効率が改善されることが期待されます。

## コミット

runtime: use 3x fewer nanotime calls in garbage collection

Cuts the number of calls from 6 to 2 in the non-debug case.

LGTM=iant R=golang-codereviews, iant CC=0intro, aram, golang-codereviews, khr https://golang.org/cl/86040043


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

[https://github.com/golang/go/commit/95ee7d6414c4a9a5da95ecb4fbefc3ac3e570d06](https://github.com/golang/go/commit/95ee7d6414c4a9a5da95ecb4fbefc3ac3e570d06)

## 元コミット内容

commit 95ee7d6414c4a9a5da95ecb4fbefc3ac3e570d06 Author: Russ Cox rsc@golang.org Date: Wed Apr 9 10:38:12 2014 -0400

runtime: use 3x fewer nanotime calls in garbage collection

Cuts the number of calls from 6 to 2 in the non-debug case.

LGTM=iant
R=golang-codereviews, iant
CC=0intro, aram, golang-codereviews, khr
https://golang.org/cl/86040043

src/pkg/runtime/mgc0.c | 16 +++++++++++-----\n 1 file changed, 11 insertions(+), 5 deletions(-)\n


## 変更の背景

Goのガベージコレクション(GC)は、アプリケーションの実行性能に直接影響を与える重要なコンポーネントです。GCの効率を最大化するためには、その実行中に発生するオーバーヘッドを最小限に抑えることが不可欠です。`runtime·nanotime()` 関数は、ナノ秒単位の精度でモノトニックな時間(システム時刻の変更に影響されない、単調増加する時間)を提供するGoランタイムの内部関数であり、主にパフォーマンス測定やタイムアウト処理などに利用されます。

この関数自体は非常に最適化されており、Linux環境ではvDSO(virtual Dynamic Shared Object)のようなメカニズムを利用して、カーネルへのシステムコールを回避し、ユーザー空間から直接実行できる場合があります。しかし、それでもなお、`nanotime()` の呼び出しにはわずかながらオーバーヘッドが存在します。特に、GCのような頻繁に実行され、かつ時間的な精度が求められる処理において、このわずかなオーバーヘッドが積み重なると、全体のパフォーマンスに無視できない影響を与える可能性があります。

このコミットの背景には、GCの実行中に不必要に `nanotime()` を呼び出すことで発生するオーバーヘッドを削減し、GCの効率をさらに向上させるという目的があります。デバッグモードではない通常の運用環境では、GCの内部的な時間計測の頻度を減らすことで、GCの実行時間を短縮し、アプリケーションのスループットを向上させることが期待されます。

## 前提知識の解説

### Goのガベージコレクション (GC)

GoのGCは、主に「マーク・アンド・スイープ」アルゴリズムをベースにしています。これは、到達可能なオブジェクトをマークし(マークフェーズ)、その後、マークされていない(到達不可能な)オブジェクトを解放する(スイープフェーズ)というプロセスです。GoのGCは、アプリケーションの実行と並行して動作する「コンカレントGC」であり、アプリケーションの一時停止(STW: Stop-The-World)時間を最小限に抑えるように設計されています。

GCの各フェーズ(マーク開始、マーク終了、スイープなど)では、そのフェーズの開始時刻や終了時刻を記録するために時間計測が行われます。この時間計測に `runtime·nanotime()` が使用されます。

### `runtime·nanotime()`

`runtime·nanotime()` は、Goランタイムが提供する内部関数で、システム起動時からの経過時間をナノ秒単位で返します。この時間は、システム時刻の変更(例: NTP同期、手動での時刻変更、サマータイム)に影響されない「モノトニッククロック」です。そのため、処理の開始から終了までの正確な経過時間を測定するのに適しています。

`nanotime()` の実装はOSに依存しますが、多くのUnix系システムでは `clock_gettime(CLOCK_MONOTONIC, ...)` を使用します。Linuxでは、vDSOという仕組みを利用して、一部のシステムコールをユーザー空間から直接実行できるように最適化されており、`nanotime()` の呼び出しオーバーヘッドを低減しています。しかし、それでも完全にゼロではなく、頻繁な呼び出しは累積的なオーバーヘッドとなります。

### `runtime·debug.gctrace`

`runtime·debug.gctrace` は、Goランタイムのデバッグフラグの一つです。このフラグが有効になっている場合、GCの実行に関する詳細なトレース情報(GCの開始・終了時刻、各フェーズの所要時間、メモリ使用量など)が標準エラー出力に表示されます。この情報は、GCのパフォーマンス分析やデバッグに非常に役立ちます。

このコミットでは、`gctrace` が有効な場合とそうでない場合で `nanotime()` の呼び出し回数を変えることで、デバッグ時の詳細な情報取得と、通常の実行時のパフォーマンス最適化を両立させています。

## 技術的詳細

このコミットの主要な変更は、`src/pkg/runtime/mgc0.c` ファイル内の `runtime·gc` 関数と `gc` 関数における `runtime·nanotime()` の呼び出し箇所の修正です。

変更前は、`runtime·gc` 関数内のループ内で `a.start_time = runtime·nanotime();` が毎回呼び出されていました。このループは、`runtime·debug.gctrace` の値に応じて1回または2回実行されます。また、`gc` 関数内でも `t1`, `t2`, `t3` の各変数に `runtime·nanotime()` の結果が代入されていました。

変更のポイントは以下の通りです。

1.  **`runtime·gc` 関数内の `nanotime` 呼び出しの最適化**:
    変更前は、`for` ループの最後に `a.start_time = runtime·nanotime();` がありました。これにより、ループが複数回実行される場合に、不要な `nanotime` 呼び出しが発生していました。
    変更後は、ループの先頭で `if(i > 0) a.start_time = runtime·nanotime();` と条件付きで呼び出すように変更されています。これにより、ループの初回(`i=0`)では `nanotime` が呼び出されず、2回目以降のループでのみ呼び出されるようになります。コミットメッセージにある「non-debug case」ではループが1回しか実行されないため、この変更により `a.start_time` への `nanotime` 呼び出しが完全に削除されます。

2.  **`gc` 関数内の `nanotime` 呼び出しの条件化**:
    変更前は、`t1`, `t2`, `t3` の各変数に無条件で `runtime·nanotime()` の結果が代入されていました。これらの変数は主にGCトレース情報のために使用されます。
    変更後は、これらの代入が `if(runtime·debug.gctrace)` という条件文で囲まれるようになりました。これにより、`runtime·debug.gctrace` が有効な場合にのみ `nanotime()` が呼び出され、それ以外の場合は `t1`, `t2`, `t3` は初期値の `0` のままとなります。

これらの変更により、`runtime·debug.gctrace` が無効な(非デバッグ)ケースでは、`runtime·gc` 関数内の `a.start_time` への呼び出しが1回削減され、`gc` 関数内の `t1`, `t2`, `t3` への呼び出しがそれぞれ1回ずつ(合計3回)削減されます。これにより、合計で4回の `nanotime` 呼び出しが削減されます。コミットメッセージの「Cuts the number of calls from 6 to 2」という記述は、GCの主要な時間計測ポイントにおける `nanotime` 呼び出しの総数を指していると考えられます。

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

```diff
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 24e4cf6816..d3a716840d 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -2316,13 +2316,13 @@ runtime·gc(int32 force)
 	// we don't need to scan gc's internal state). Also an
 	// enabler for copyable stacks.
 	for(i = 0; i < (runtime·debug.gctrace > 1 ? 2 : 1); i++) {
+		if(i > 0)
+			a.start_time = runtime·nanotime();
 		// switch to g0, call gc(&a), then switch back
 		g->param = &a;
 		g->status = Gwaiting;
 		g->waitreason = "garbage collection";
 		runtime·mcall(mgc);
-		// record a new start time in case we're going around again
-		a.start_time = runtime·nanotime();
 	}
 
 	// all done
@@ -2378,7 +2378,9 @@ gc(struct gc_args *args)
 	itabtype = ((PtrType*)eface.type)->elem;
 	}\n
-\tt1 = runtime·nanotime();
+\tt1 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt1 = runtime·nanotime();

 // ... (中略) ...

-\tt2 = runtime·nanotime();
+\tt2 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt2 = runtime·nanotime();

 // ... (中略) ...

-\tt3 = runtime·nanotime();
+\tt3 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt3 = runtime·nanotime();

 \tbufferList[m->helpgc].busy = 0;
 \tif(work.nproc > 1)

コアとなるコードの解説

src/pkg/runtime/mgc0.c

runtime·gc 関数内の変更

 	for(i = 0; i < (runtime·debug.gctrace > 1 ? 2 : 1); i++) {
+		if(i > 0)
+			a.start_time = runtime·nanotime();
 		// switch to g0, call gc(&a), then switch back
 		g->param = &a;
 		g->status = Gwaiting;
 		g->waitreason = "garbage collection";
 		runtime·mcall(mgc);
-		// record a new start time in case we're going around again
-		a.start_time = runtime·nanotime();
 	}
  • 変更前: for ループの最後に a.start_time = runtime·nanotime(); がありました。この行は、ループが複数回実行される場合に、各イテレーションの最後に nanotime を呼び出して start_time を更新していました。
  • 変更後: 既存の a.start_time = runtime·nanotime(); の行が削除され、ループの先頭に if(i > 0) a.start_time = runtime·nanotime(); が追加されました。
    • runtime·debug.gctrace1 以下の場合(非デバッグケース)、ループは1回のみ実行されます(i=0)。この場合、if(i > 0) の条件は偽となるため、a.start_timenanotime() で更新されません。これにより、この場所での nanotime 呼び出しが1回削減されます。
    • runtime·debug.gctrace2 の場合(デバッグケース)、ループは2回実行されます。初回(i=0)では nanotime は呼び出されず、2回目(i=1)で nanotime が呼び出され a.start_time が更新されます。これにより、デバッグケースでも不必要な呼び出しが抑制されます。

gc 関数内の変更

-\tt1 = runtime·nanotime();
+\tt1 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt1 = runtime·nanotime();

 // ... (中略) ...

-\tt2 = runtime·nanotime();
+\tt2 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt2 = runtime·nanotime();

 // ... (中略) ...

-\tt3 = runtime·nanotime();
+\tt3 = 0;
+\tif(runtime·debug.gctrace)
+\t\tt3 = runtime·nanotime();
  • 変更前: t1, t2, t3 の各変数に無条件で runtime·nanotime() の結果が代入されていました。これらの変数はGCの各フェーズの開始・終了時刻を記録するために使用され、主に gctrace 出力に利用されます。
  • 変更後: 各 nanotime() の呼び出しが if(runtime·debug.gctrace) という条件文で囲まれました。
    • runtime·debug.gctrace が有効な場合のみ、nanotime() が呼び出され、t1, t2, t3 に時刻が記録されます。
    • runtime·debug.gctrace が無効な場合、t1, t2, t3 は初期値の 0 のままとなり、nanotime() の呼び出しは行われません。これにより、非デバッグケースでの nanotime 呼び出しが3回削減されます。

これらの変更により、非デバッグケースでは合計で4回の nanotime 呼び出しが削減され、GCのオーバーヘッドが低減されます。

関連リンク

参考にした情報源リンク

  • Go runtime.nanotime() overhead: Web検索結果 (Google Search)
  • Go Garbage Collection Documentation (一般的なGo GCの知識)
  • Go Source Code (runtime/mgc0.cの分析)