MPI と OpenMP* の併用によりハードウェアを活用する

同カテゴリーの次の記事

ハイブリッド・アプリケーション: インテル® MPI ライブラリーと OpenMP*

この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Mixing MPI and OpenMP, hugging hardware and dealing with it」の日本語参考訳です。


今朝、私は珍しく時間がとれたため、Supercomputing のチュートリアル・プログラムに参加しました。これは非常に有意義なものでした。

このチュートリアルは、Rolf Rabenseifner 氏 (シュトゥットガルト大学)、Georg Hager 氏 (エルランゲン大学)、および Gabriele Jost 氏 (テキサス大学テキサス先端コンピューティング・センター (TACC)/海軍大学院) による、1 つのプログラムで MPI と OpenMP* を併用する利点と欠点についてのものでした。

少し前に行われた初期の実験では、MPI と OpenMP* の併用は取り組むだけの価値があるかどうか分かりませんでした。しかし、ソケット数の増加、ソケットごとのコア数の増加、そしてコアごとのハードウェア・スレッドの増加に伴い、ノードでより多くの SMP 並列性が利用できるようになり、すべてが変わりました。

このチュートリアルで最も印象的だったのは、チュートリアルの大半が今では当たり前の事実となっている多数の「ささいなこと」で占められていたことです。これらは将来なくなるでしょうか?  恐らくなくなるでしょう。しかし、これらの事実は現在我々が置かれている状況を教えてくれます。

これは非常に興味深いことです。以下に、チュートリアルで取り上げられていた、OpenMP* と MPI の併用を考える上で重要な「ささいなこと」のリストを示します。

  • MPI 実装はスレッドセーフか? (ノード上の) MPI のインスタンス内でスレッド (OpenMP*) を使用するには、より新しいバージョン、特別なリンクオプション、そして特別なロックが必要になることがあります。これに関して、MPI と OpenMP* には驚くほど多数のオプションがあり、その多くは各システムのドキュメントにあります。
  • アムダールの法則 – MPI の送信/受信がノードごとに 1 回行われる場合、その他のすべてのスレッドは恐らくアイドル状態になるため、シリアル実行によるプログラム通信でボトルネックが発生します。チュートリアルではこれを軽減するため、各ノードからの MPI 接続を増やし、より多くのスレッド間で通信を分割したり、通信と並列に作業を行うといったアイデアを含むいくつかの例が紹介されました。講師陣によると、これは「非常に困難」とのことです。
  • コンパイラーが OpenMP* を認識している – これは最適化を低下させる可能性があります。これは、恐らく皆さんが望んでいないことでしょう。チュートリアルでは、IBM* Power6 コンパイラーは最適化レベル 4 (-O4) まで上げない限り、パフォーマンスを向上できない例が紹介されました。
  • すべてのプログラムにロード・バランスが必要なわけではない – 多くの場合、ロード・バランスにより効果が得られるプログラムでは、プログラミングの労力を費やすことでより良い結果が得られます。そうでないプログラムでは、労力を費やしてもそれだけのメリットが得られないことがあります。
  • MPI にすべてを自動で行わせることはできない – OpenMP* を使用しないことで、MPI の不要なオーバーヘッドが減ることを期待している方もいるかもしれません。これは状況によっては可能です。通常は最適になりますが、いわゆる「ミスマッチ」のため目覚しい効果が得られることもあります。完全にロードバランスのとれたアプリケーションでは、OpenMP* を無視し、どこにでも MPI だけを使用することができます。
  • ccNUMA は分かりにくい – メモリーの割り当て場所 (本当にローカルかどうか) により、パフォーマンスに大きな影響を及ぼします。最も一般的な方法は、最初に使用するもの (「First Touch」) に対して最もローカルな場所を割り当てることです。初期化関数と実際の利用が異なる場合は、驚くべき結果になることがあります。この場合、「驚くべき結果」は最適ではない割り当てにつながります。そして、「特定の API を使用してメモリーの割り当てを制御する」ように要求されることになります。これはエキスパートには役立つかもしれませんが、問題をより複雑なコードに委ねることになります。
  • オーバーサブスクリプション – 状況によっては、プログラムが間接的に必要以上のスレッドを生成してしまうことがあります。しかし、もっと大きな問題は、その他のコアがアイドル状態にもかかわらず、一部のコアでオーバーサブスクリプションが発生することです。この場合、「特定の API を使用してスレッドの割り当てを制御する」ように要求されることになります。この場合も、開発者が必要な作業を行えるように、すぐにソリューションが必要です。
  • 利用可能なメモリーバンド幅とメモリーアクセスを行うスレッド – 一部のノードでは、利用可能なコア数 (またはハードウェア・スレッド数) よりも少ないスレッド数でメモリーが完全に飽和します。これにより、ノードごとのスレッド数を減らしたい (「特定の API を使用して生成されるスレッド数を制御する」) と考えるでしょう。MPI/OpenMP* を併用するプログラムでは、最も簡単にこれを実装できます – ノード上の OpenMP* スレッドプールのスレッド数を減らすだけです。
  • データのコピーは (全体の) メモリー・フットプリントを非常に大きくし、大幅なプラスまたはマイナスになる – ローカルコピーは 「非共有」というアイデアを実現する上で役立ちますが、コピーにはコストがかかり、メモリー使用量も増えます – どちらもそれだけの価値がないか、大幅なマイナス影響がある可能性があります。適正なバランスを見つけることは、コンピューター・サイエンスの技の 1 つといえるでしょう。コピーは MPI インスタンスごとに行われ、OpenMP* は 1 つのコピーを共有する傾向にあり、開発者は多くの場合その扱いについて合理的な方法を知っているため、OpenMP* + MPI の方がどちらかといえば有利です。これは、特定のコピーモデルが暗示または奨励されるという、ハイブリッド・モデルのプログラミングにおける興味深い副作用 (少なくとも私にとっては) といえます。
  • これらすべてにより不安定なパフォーマンスになる – 多くの場合、問題サイズ、ファイル・バッファー・サイズ、ノード数に応じて、非常に良いパフォーマンスが得られたり、あるいはパフォーマンスが大幅に低下したりするため、イライラすることでしょう。対応方法が見つかるまで、我々はより不安定なパフォーマンスの時代に突入したといえます。これは、キャッシュにとらわれないアルゴリズムが登場する前の、キャッシュ向けにコードをチューニングしていた頃を思い出させます。しかし、そのときは問題は 1 つで、1 つの手法で解決することができました。今回のチュートリアルによれば、我々は大量の問題に直面しており、それぞれ別々のソリューションが必要になるようです。ひょっとしたら、今が夜明け前の一番暗い時期なのかもしれません。

このチュートリアルはためになるものでした。例年の Supercomputing カンファレンスと同様に、実現が困難なもの を理解することで、今日の状況 を学んだような気がします。OpenMP*/MPI の併用は悪くはないものの、将来改善の余地が多数あることが分かりました。

チュートリアルの最後に講師陣は、「これは純粋な MPI 実装ほど簡単ではありませんが、今すぐに取り掛かることができます。ハイブリッド・ソリューションの効率はフリーではありません – ソースコード開発に費やした労力に大きく依存します。」と締めくくりました。

つまり、エキスパートには仕事の機会があるといえます。
しばらくは仕事に困ることはなさそうですね。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事