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

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

このコミットは、Go言語の公式FAQドキュメントである doc/go_faq.html ファイルに対する変更です。具体的には、「GOMAXPROCSを1より大きくしても、なぜプログラムが速くならないことがあるのか?」という質問に対する回答をより詳細に、かつ明確にするための加筆修正が行われています。

コミット

01afb79c5960c746238c21b1eadc222af85d7c19

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

https://github.com/golang/go/commit/01afb79c5960c746238c21b1eadc222af85d7c19

元コミット内容

FAQ: more words about why GOMAXPROCS>1 might not speed you up

R=golang-dev, adg, gri
CC=golang-dev
https://golang.org/cl/5572067

変更の背景

Go言語は並行処理を容易にするための強力な機能(ゴルーチン、チャネル)を提供していますが、これらの機能が常にパフォーマンス向上に繋がるわけではないという誤解がありました。特に、GOMAXPROCS 環境変数を1より大きく設定することで、自動的にプログラムが高速化されると考える開発者がいたため、その誤解を解消し、Goの並行処理モデルと実際の並列実行の間の関係について、より正確な情報を提供する必要がありました。

このコミットは、GoのFAQドキュメントにおいて、GOMAXPROCS の設定がパフォーマンスに与える影響、特に並行性と並列性の違い、そしてチャネル通信のオーバーヘッドがパフォーマンスに与える影響について、より詳細な説明を加えることを目的としています。これにより、開発者がGoプログラムのパフォーマンス特性をより深く理解し、適切な設計とチューニングを行えるようにすることが意図されています。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の概念と一般的なコンピュータサイエンスの知識が必要です。

  • ゴルーチン (Goroutine): Go言語における軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。GoランタイムがゴルーチンをOSスレッドにマッピングし、スケジューリングを行います。
  • チャネル (Channel): ゴルーチン間で値を安全に送受信するための通信メカニズムです。チャネルは、Goにおける並行処理の主要な同期プリミティブであり、共有メモリによる競合状態を避けるための推奨される方法です。
  • GOMAXPROCS: Goランタイムが同時に実行できるOSスレッドの最大数を制御する環境変数です。デフォルトでは、利用可能なCPUコア数に設定されます。GOMAXPROCS=1 の場合、Goランタイムは単一のOSスレッドしか使用せず、すべてのゴルーチンはそのスレッド上で多重化されて実行されます。GOMAXPROCS > 1 の場合、複数のOSスレッドが利用され、ゴルーチンがこれらのスレッドに分散して実行されることで、真の並列実行が可能になります。
  • 並行性 (Concurrency) と並列性 (Parallelism):
    • 並行性: 複数のタスクが「同時に進行しているように見える」特性を指します。これは、単一のCPUコア上でタスクを高速に切り替える(タイムスライス)ことによって実現できます。Goのゴルーチンとチャネルは、並行処理を容易にするためのものです。
    • 並列性: 複数のタスクが「実際に同時に実行されている」特性を指します。これは、複数のCPUコアやプロセッサが存在する場合にのみ可能です。GOMAXPROCS > 1 の設定は、Goプログラムが並列性を活用できるようにするためのものです。
  • コンテキストスイッチ (Context Switching): OSがCPUの実行コンテキストをあるプロセス(またはスレッド)から別のプロセス(またはスレッド)に切り替える操作です。これには、現在のプロセスの状態(レジスタ、プログラムカウンタなど)を保存し、新しいプロセスの状態をロードするオーバーヘッドが伴います。頻繁なコンテキストスイッチは、パフォーマンスの低下を引き起こす可能性があります。
  • プライムシーブ (Prime Sieve) 例: Goの公式仕様やドキュメントでよく用いられる、チャネルとゴルーチンを使った素数生成の例です。この例は、多くのゴルーチンとチャネル通信を伴いますが、本質的には逐次的な処理であり、真の並列性を持つわけではありません。

技術的詳細

このコミットは、GOMAXPROCS の設定とGoプログラムのパフォーマンスに関する重要な洞察を提供しています。

Goのランタイムは、ゴルーチンをOSスレッドにマッピングし、スケジューリングを行います。GOMAXPROCS は、このOSスレッドの最大数を決定します。一般的に、GOMAXPROCS をCPUコア数に設定することで、CPUバウンドなワークロードにおいて最大の並列性を引き出すことができます。しかし、このコミットが強調しているのは、すべての並行プログラムが並列実行によって高速化されるわけではないという点です。

変更されたFAQの記述は、以下の点を明確にしています。

  1. 本質的に逐次的な問題は並列化できない: プログラムが本質的に逐次的な処理である場合、たとえ多くのゴルーチンを起動したとしても、それらを並列に実行しても速度は向上しません。並行性は、問題が本質的に並列である場合にのみ、並列性へと変換されます。
  2. チャネル通信のオーバーヘッド: ゴルーチンが計算よりもチャネル通信に多くの時間を費やすプログラムでは、複数のOSスレッド(GOMAXPROCS > 1)を使用すると、パフォーマンスが低下する可能性があります。これは、スレッド間でデータを送信する際に発生するコンテキストスイッチのコストが非常に大きいためです。Goランタイムは、ゴルーチンをOSスレッド間で移動させる際に、このコンテキストスイッチのオーバーヘッドを発生させます。
  3. プライムシーブの例: Goの仕様にあるプライムシーブの例は、多くのゴルーチンを起動しますが、その処理は本質的に逐次的であり、真の並列性を持っていません。この種のプログラムでは、GOMAXPROCS を増やすと、むしろパフォーマンスが低下する可能性が高いと指摘されています。これは、頻繁なチャネル通信によるコンテキストスイッチのオーバーヘッドが、並列実行の潜在的な利点を上回るためです。

要するに、GOMAXPROCS を増やすことは、プログラムが真に並列なタスクを多数持ち、それらのタスク間の通信が少ない場合にのみ有効です。チャネル通信が頻繁に発生し、ゴルーチンが計算よりも通信に時間を費やすようなI/Oバウンドまたは通信バウンドなプログラムでは、GOMAXPROCS を増やすことが逆効果になることがあります。このような場合、GOMAXPROCS=1 の方が、コンテキストスイッチのオーバーヘッドを最小限に抑え、より良いパフォーマンスを示すことがあります。

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

--- a/doc/go_faq.html
+++ b/doc/go_faq.html
@@ -1020,10 +1020,23 @@ slower?</h3>
 
 <p>
 It depends on the nature of your program. 
-Programs that contain several goroutines that spend a lot of time
-communicating on channels will experience performance degradation when using
-multiple OS threads. This is because of the significant context-switching
-penalty involved in sending data between threads.
+Problems that are intrinsically sequential cannot be sped up by adding
+more goroutines.
+Concurrency only becomes parallelism when the problem is
+intrinsically parallel.
+</p>
+\n+<p>
+In practical terms, programs that spend more time
+communicating on channels than doing computation
+will experience performance degradation when using
+multiple OS threads.
+This is because sending data between threads involves switching
+contexts, which has significant cost.
+For instance, the <a href="/doc/go_spec.html#An_example_package">prime sieve example</a>
+from the Go specification has no significant parallelism although it launches many
+goroutines; increasing <code>GOMAXPROCS</code> is more likely to slow it down than
+to speed it up.
 </p>
 
 <p>

コアとなるコードの解説

変更は doc/go_faq.html 内の「GOMAXPROCSを1より大きくしても、なぜプログラムが速くならないことがあるのか?」という質問に対する回答部分です。

変更前: 変更前の記述は、チャネル通信に多くの時間を費やすゴルーチンを含むプログラムが、複数のOSスレッドを使用するとパフォーマンスが低下する可能性があることを指摘していました。その理由として、スレッド間のデータ送信に伴う「顕著なコンテキストスイッチのペナルティ」を挙げていました。

変更後: 変更後の記述は、この説明をより詳細かつ一般化しています。

  1. **「本質的に逐次的な問題は、ゴルーチンを追加しても高速化できない」**という原則が追加されました。これは、並行性と並列性の根本的な違いを強調しています。
  2. **「問題が本質的に並列である場合にのみ、並行性は並列性になる」**と明確に述べられています。これにより、Goの並行処理機能がどのように並列実行に繋がるのかの条件が示されています。
  3. **「計算よりもチャネル通信に多くの時間を費やすプログラムは、複数のOSスレッドを使用するとパフォーマンスが低下する」**という実用的な観点からの説明が維持されています。
  4. パフォーマンス低下の理由として、**「スレッド間のデータ送信にはコンテキストスイッチが伴い、それが大きなコストとなる」**と、より簡潔かつ明確に説明されています。
  5. 具体例として、**「Goの仕様にあるプライムシーブの例は、多くのゴルーチンを起動するが、真の並列性を持たない」ことが挙げられています。そして、「GOMAXPROCSを増やすと、むしろ速度が低下する可能性が高い」**と結論付けられています。

この変更により、FAQの回答は、単にチャネル通信のオーバーヘッドを指摘するだけでなく、並行性と並列性の概念、そしてプログラムの性質(逐次的か並列的か、計算バウンドか通信バウンドか)が GOMAXPROCS の効果にどのように影響するかについて、より包括的で教育的な内容になっています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントおよびFAQ
  • Go言語のソースコード(doc/go_faq.html
  • Go言語における並行性と並列性に関する一般的な知識
  • コンテキストスイッチに関する一般的なコンピュータサイエンスの知識