並列パフォーマンスの理解 パート 2
この記事は、Dr.Dobb’s に掲載されている「Understanding Parallel Performance」の日本語参考訳です。
著者紹介: Herb Sutter 氏 - ソフトウェア開発トピックにおいてベストセラー著者でコンサルタント。また Microsoft 社でソフトウェア・アーキテクトを務める。コンタクト先: www.gotw.ca
並列パフォーマンスの理解: どうしたら十分な並列パフォーマンスかどうか判断できるのか?
コンテキストスイッチの軽減
結果をまとめている最後の行でただ単に .value() を 3 回呼び出しただけでは、呼び出されたスレッドが 2 回もスリープ状態からウェイクアップし、直後にまたスリープ状態に戻される可能性がある。future のライブラリーに “future のグループを待機する” 機能がある場合は、代わりにその機能を使用することで無駄なウェイクアップを排除し、いくつかのコンテキストスイッチを減らすことができる。
また、呼び出されたスレッドは結果を待つ以外に何もしないことが分かる。オリジナルのスレッドを無駄に待機させておく代わりに、最後の処理のチャンク (タスク) を残しておいて実行することでスイッチを排除できる。
以下は、この 2 つの手法を使用したコードである。
// Example 4: Improved parallel code for MyApp 2.0 // int NetSales() { // perform the subcomputations concurrently future wholesale = pool.run( [] { CalcWholesale(); } ); future retail = pool.run( [] { CalcRetail(); } ); int returns = TotalReturns(); // keep the tail work // now block for the results—-wait once, not twice wait_all( wholesale, retail ); return wholesale.value() + retail.value() - returns; }
当然のことながら、ここで重要なのはオーバーヘッドの合計ではなく、それが作業全体に対してどれだけ大きいかである。各タスクのコストは、同期的ではなく非同期的に実行した場合のコストよりもかなり大きくなければならない。
並行性が活用されない場合のコスト
現代の環境において重要なコストは、並行性が活用されない場合のコストである。ここで使用しているサンプルの並列アルゴリズムがシリアル実行された場合、並列コードのコストはシーケンシャル・コードのコストとどの程度違うのだろうか?
例えば、並列コードをシングルコア・マシンで実行した場合、タスクはシーケンシャルに実行され、並行性が活用されない (つまり、スレッドプールにはスレッドが 1 つしかない)。この場合、コストはどうなるのだろう? 並列コードでは並行性を表現するためにオーバーヘッドが加わっているが、特定のシステムでコードが並列実行されなかったとしてもこのオーバーヘッドは発生する。
ここでは、Example 1 (MyApp version 1.0) がシリアルコードで、Example 4 (MyApp 2.0) が並列コードである。マルチコアマシンでは Example 4 のほうが Example 1 よりも速いが、シングルコア・マシンでは Example 1 のほうが Example 4 よりも速くなる。シングルコア・マシンでこのオーバーヘッドを軽減するには、粒度を調整してタスクのサイズを大きくし、その数を減らすことだ。あるいは、シーケンシャル実装に切り替えるのも 1 つの方法である。