GHCはSMP(対称型マルチプロセッサ)上でのHaskellプログラムの並列実行をサポートしている。
並行性(concurrency)と並列性(parallelism)の間には明確な区別がある。並列性は、専ら、複数のプロセッサを同時に使うことでプログラムを速くすることについてである。一方、並行性は抽象化の一手段である。具体的には、複数の非同期のイベントに対応せねばならないプログラムを構築するための便利な方法である。
とはいうものの、この二つの用語は確かに関連している。複数のCPUを使うことで、複数の並行スレッドを並列に実行することができるようになる。GHCのSMP並列計算サポートはまさにこれを行う。しかし、並行性を利用しないプログラムにおいて、並列計算によってパフォーマンスを改善することも、また可能である。この節では、GHCを使って並列プログラムをコンパイル・実行する方法を解説し、7.18. Concurrent HaskellおよびParallel Haskellでは並列性に影響する言語機能を説明する。
複数のCPUを利用するには、プログラムが-threaded
オプション付きでリンクされていなればならない。(4.11.6. リンクに影響するオプションを見よ)。加えて、以下のコンパイラオプションが並列性に影響する。
-feager-blackholing
ブラックホール化(blackholing)とは、サンク(遅延された計算)に、評価中という印を付ける行為である。これは三つの理由で有用である。第一に、これによって、ある種の無限ループが検出(NonTermination
例外)できる。第二に、ある種のスペースリークを避けることができる。第三に、並列プログラムにおいて一つの計算を繰り返し行うことを避けることができる。これは、計算が既に実行中である場合にそうと分かるからである。
-feager-blackholing
オプションは、それぞれのサンクの評価が始まってすぐにそれをブラックホール化するようにする。デフォルトは「遅延ブラックホール化」であり、こちらでは、スレッドが何らかの理由で一時停止した場合にのみサンクに評価中の印が付けられる。遅延ブラックホール化において、大部分のサンクはブラックホール化される必要がないので、典型的にはこちらの方が効率的(1-2%程度)である。しかし、並列プログラムでは積極的ブラックホール化によってより多くの計算の繰り返しを防ぐことができ、これが実際並列性にとって重要である場合がよくある。
我々は、並列に走ることを意図したコードは何でも-feager-blackholing
付きでコンパイルすることを推奨する。
プログラムを複数のCPU上で走らせるには、RTSの-N
オプションを使う。
-N[x
]
プログラムの実行中、同時にx
個のスレッドを使う。通常、x
は機械のCPUコア数と一致するべきである[9]。例えば、デュアルコアの機械ではおそらく+RTS -N2 -RTS
を使うことになるだろう。
x
を省略する、つまり+RTS -N -RTS
とすると、マシンにあるプロセッサの数に基いてランタイムが自分で値を決定する。
マシンの全てのプロセッサを使う時には注意すること。もし他のプログラムがプロセッサをいくつか使っているなら、これによってパフォーマンスを改善するどころか悪影響になることがある。
-N
を設定することには、並列ガベッジコレクタを有効にするという効果もある(4.16.3. ガベッジコレクタを制御するためのRTSオプションを見よ)。
プログラムが開始した後にこの値を変更する方法は(今のところ)ない。
現在の-N
オプションの値は、GHC.Conc.numCapabilities
でHaskellプログラムから得られる。
以下のオプションは、ランタイムがスレッドをCPUに割り振る方法に影響する。
-qa
OSのaffinity(親和性)機能を使ってOSスレッドをCPUコアに固定する。これは実験的な機能であり、有用かもしれないしそうでないかもしれない。役に立ったかどうか知らせてね!
-qm
負荷分散のための自動移住を無効にする。通常ランタイムは、暇なCPUを活用するため、CPUをまたいでスレッドをスケジュールしようとする。このオプションはこの振る舞いを無効にするものである。移住はスレッドにしか適用されないことに注意。par
によって作られたスパークは、別途work-stealingによって負荷分散される。
このオプションが役に立つのは、おそらく、並行プログラムで、GHC.Conc.forkOnIO
を使ってスレッドを明示的にCPUにスケジュールする場合だけだろう。
-qw
スレッドが目覚めたとき、それを現在のCPUに移住させる。通常、スレッドがブロックしていて目覚めた場合、最後に走っていたCPUに割り振られる。このオプションによって、スレッドがブロックを解いたCPUに即座に移住できるようになる。
この積極的な移住を可能にする根拠となるのは、これによって相互に連絡し合うスレッドが同じCPUに集められる傾向があることである。しかし、これが悪い戦略になるような病理的な例が存在する。プログラム中の連絡パターンによって、これは良案になるかもしれず、そうでないかもしれない。
プログラムを実行するときに-s
というRTSオプションを加えると、時間統計情報を見ることができる。これを使って、使うCPUの数を増やしたことでプログラムが速くなったかどうかを確認できる。ユーザ(user)時間が消費(elapsed)時間よりも大きいなら、プログラムは単一のCPUよりも多くを使ったことになる。比較のために-N
オプションなしでも走らせてみると良いだろう。
+RTS -s
の出力によって、プログラムの実行中にいくつの「スパーク」(spark)が作られ、実行されたかが分かる(4.16.3. ガベッジコレクタを制御するためのRTSオプションを見よ)。これによって、あなたのpar
注釈がどの程度うまく働いているかについての感触が得られるだろう。
たくさんの実験とランタイムシステムのチューニングにより、GHCの並列性サポートは6.12.1で改善された。我々は、これがあなたにとってどの程度うまく働くかを聴きたいと引き続き思っている。また、我々のベンチマーク一式に加えるための並列プログラムも収集したいと思っている。
hyperthreadingコアの数を含めるべきかどうかは未解決の問題である。遠慮なく実験して結果を知らせてほしい。