makeの並列オプションは何を指定するべきか

最近何の縁があってか 鬼斬弐 というオープンソースのプロセッサシミュレータの開発を手伝っています。

github.com

ここ数日でRISC-VのRV64Gへの対応が急速に進められ、ベンチマークプログラムも動くものが増えてきたようです。 私はちまちま細かいバグを取り除く作業をやっています。


今日は、makeを並列で行うオプション-j付きで実行するとき、その引数(最大何並列にするか)を何にすべきか調査した話をします。

よく見かけるのは、以下のような言説です。

  • CPUのスレッド数を指定するべき

これはもっとも単純で、CPUの動かせるスレッド数より並列度を高くしても、本当に並列に実行できるわけではないので、これが最大の効率になるはず、といった考えから来るものでしょう。

  • CPUのコア数を指定するべき

同時マルチスレッディングで複数のスレッドが動かせるといっても、あくまで1つのCPUで行っているため、演算器が余っていなければ特に速くなることはありません。それどころか、キャッシュを複数のプロセスで共有するため、運が悪ければ並列にした方が遅くなることまであり得ます。コア数までの並列度なら、全てのプロセスが独立したコア上で動くため、そのような問題が発生することはないはずです。

  • CPUのスレッド数(あるいはコア数)より1少ない数を指定するべき

makeを行っている間、CPUはコンパイルだけを行うのではなく、OSの仕事やバックグラウンドプロセスなどを実行する必要もあります。そこで、そういったプロセスのために1スレッド(1コア)残しておこうといった発想だと思われます。実際、スレッド数以上の並列度を指定するとマウスやキーボードの反応が悪くなって他の作業をすることがほとんど不可能になることもあるので、多少コンパイルが遅くなってもよいという場合には悪くない、安定感のある選択肢でしょう。

  • CPUのスレッド数(あるいはコア数)より1大きい数を指定するべき

これはおそらく、あるプロセスですべきことが終わった瞬間、OSが次のプロセスを選ぶわけですが、このときに選ぶプロセスが存在しないと無駄な時間が発生するため、一つ余分に作っておこうという発想だと思われます。プロセスを立てるのに時間がかかる環境(CygwinやWSL)で有効な作戦かもしれません。コア数と書かれている場合もありますが、どういった理論によるものなのかはよくわかりません。論理コア数といった意味なのでしょうか?(この記事では、スレッド数=論理コア数、コア数=物理コア数、の意味です。)

  • 特に指定する必要はない

指定しなかった場合、プロセス数の上限がない(無限にプロセスを立てることができる)という指定をしたことになります。軽量な処理を大量に行う場合は、プロセスを立てるオーバーヘッドを隠すためにこのような指定が有効な場合もあるでしょう。ただし、コンパイルは重い処理でありかつメモリを大量に消費するため、無制限にプロセスを立てるとメモリ不足を引き起こし、スワップが発生してものすごく遅くなる可能性があります。そのため普通は何らかの数字を指定するのが一般的です。

実験

先ほど話した、鬼斬弐をmakeする時間を測ることによって、最適なオプションを探します。 使うのはmasterの現在の最新 662dea7501d48f6cd70ace0ba39d1ebf3a089715 です。一度makeをすることによりファイルをキャッシュに乗せた後、make cleanします。その後makeを並列オプションを変えて実行時間を計測し、毎回make cleanします。これにより、実行時間の計測は再現性の高い精度(誤差1%未満)で行うことができます。もしメジャーフォルトが発生した場合にはファイルがキャッシュから追い出されている可能性が高いため、その後の計測に悪影響を与える可能性があります。その場合、この手順を最初から行うことによりなるべく条件を均一にします。

計測は、E5-2603 v3(1.6GHz, 6Core 12Thread)上、ネイティブのGNU/Linuxで行いました。より多くのCPUの種類、環境での計測は今後の課題とさせてください。 また、コンパイルは二週間前に作ったgcc8.2.0(-O3 -march=nativeでセルフホストコンパイル済み)を使いました。コンパイルするだけならもっと古いバージョンのgccを使った方が速いようです。

オプション 実行時間 備考
指定なし(直列) 447秒
-j 2 235秒
-j 3 163秒
-j 4 128秒
-j 5 108秒
-j 6 95.2秒 コア数
-j 7 93.9秒
-j 8 94.4秒
-j 9 95.6秒
-j 10 95.2秒
-j 11 95.6秒
-j 12 95.8秒 スレッド数
-j 24 97.7秒
-j 98.1秒 メジャーフォルト発生するもスワップはなし

実験結果はこのようになりました。どうやらコンパイルする仕事は同時マルチスレッディングを使うとかえって遅くなる仕事であるようです。驚くべきことに、並列オプションは-j 7が最も速くコンパイルできるという結果になりました。コア数+1の設定が良いという言説はなかなか理解しがたいものですが、実験してみると正しいようです。理由はよくわかりませんが……。

-jを行った場合、メジャーフォルトが発生しました。しかし、スワップは起こらなかったので、単にキャッシュを追い出したに過ぎないようです。とはいえ、OSの仕事が増えてしまったため時間が余計にかかっています(systemの実行時間が、他の場合は40秒程度に対して-jの場合のみ46秒)。鬼斬弐のコンパイルでは150プロセス程度しか同時に実行されないため劇的な性能低下はまぬがれましたが、もっと大規模なものをビルドする場合はスワップが発生して悲惨なことになるものと思われます。このようにスワップが発生しないとわかっているのであれば、よく考えずに-jを指定しても最適なオプション指定と大差ないため、最適なオプションが分からないときはとりあえず-jを指定しておく、というのもあながち間違いとは言い切れない感じがあります。

今年ももう終わりですね。皆様よいお年を。