並列パフォーマンスの理解 パート 2

同カテゴリーの次の記事

並列パフォーマンスの理解 パート 1

この記事は、Dr.Dobb’s に掲載されている「Understanding Parallel Performance」の日本語参考訳です。

著者紹介: Herb Sutter 氏 - ソフトウェア開発トピックにおいてベストセラー著者でコンサルタント。また Microsoft 社でソフトウェア・アーキテクトを務める。コンタクト先: www.gotw.ca


編集注記:
本記事は、2012 年 6 月 11 日に公開されたものを、加筆・修正したものです。
この記事は並列プログラミングに取り組む開発者が、最初に取り組むべき課題と解決方法が明確に示されています。記事の初出は 2011 年ですが、現代でも十分に当てはまる内容です。

並列パフォーマンスの理解: どうしたら並列パフォーマンスが妥当であるか判断できるのか?

パート1 | パート2 | パート3

コンテキストスイッチの軽減

結果をまとめている最後の行でただ .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 つの方法である。

パート1 | パート2 | パート3

関連記事