SYCL* の事例

インテル® DPC++/C++ コンパイラー

この記事は、The Parallel Universe Magazine 51 号に掲載されている「The Case for SYCL*」の日本語参考訳です。原文は更新される可能性があります。原文と翻訳文の内容が異なる場合は原文を優先してください。


parallel_v51_01

計算科学に長く携わっていると、プログラミング言語やツールは移り変わるものだということが分かります。1 つのプログラミング言語だけで仕事ができる人は、もうほとんどいないでしょう。2000年にインテルに入社したとき、私のプロジェクトでは、Fortran、C、そして残念なことに C++ が混在していました。当時、私は C++ が好きではありませんでした。コンパイルに時間がかかり、オブジェクトやオブジェクト指向プログラミングに価値があるとは思えなかったのです。C++ の構成要素は言語に豊かな表現力をもたらす一方、処理速度が遅くなる1 ことも、魅力を感じなかった要因です。

そのため、oneAPI を使うようになり、C++ を勉強し直さなければならなくなったときには、気が重くなりました。oneAPI のダイレクト・プログラミング手法は、ISO C++17 をベースとした Khronos SYCL* (英語) を採用しています。しかし、2000年頃の C++ と比較すると、C++17 の見た目は大きく異なります。特に C++ 標準テンプレート・ライブラリー (STL) や oneAPI ライブラリーと組み合わせた場合、非常に表現力が豊かで、生産性に優れていると言わざるを得ません。これが、本記事のトピックである SYCL*、および SYCL* と ISO C++ の関係につながります。

C++ は、(Rust* 支持者が何と言おうと2) 時の試練に耐えてきた優れたプログラミング言語です。多くの C++ プログラマーがおり、何十億行もの C++ コードが存在します。しかし、「フリーランチ」 (英語) はずいぶん前に終わり、Herb Sutter は現在「ハードウェア・ジャングル3 (英語) について語っています。ヘテロジニアス並列処理時代にありながら、C++ にはアクセラレーター・デバイスを活用するのに必要な主要機能が欠けています。

まず、最も重要なことですが、C++ にはアクセラレーター・デバイスという概念がないため、利用可能なデバイスを検出したり、それらのデバイスにワークをオフロードしたり、デバイス上で実行中のコードで発生する例外を処理する言語構造がありません。コードがホストとさまざまなデバイス上で非同期に実行される場合、例外処理は特に複雑になります。SYCL* は、利用可能なデバイスを検出し、デバイスのハードウェアとバックエンドの特性を照会する platform (英語)、context (英語)、および device (英語) の C++ クラスを提供しています。queue (英語) クラスと event (英語) クラスは、プログラマーがデバイスにワークをサブミットし、非同期タスクの完了を監視できるようにします。デバイス上で実行されているコードでエラーが発生した場合、queue::wait_and_throw()、queue::throw_asynchronous()、または event::wait_and_throw() メソッドによって、あるいはキューやコンテキストを破棄するときに自動的に非同期のエラーハンドラーをトリガーできます。

また、C++ には不連続メモリーという概念がありません。アクセラレーター・デバイスは通常、ホストシステムのメインメモリーとは別に独自のメモリーを持っています。ヘテロジニアス並列処理を行うには、ホストメモリーからデバイスメモリーにデータを転送する必要があります。C++ にはそのようなメカニズムはありませんが、SYCL* ではバッファー (英語)/アクセサー (英語) とポインターベースの統合共有メモリー (USM) (英語) への割り当てという形で提供されています。4ホストとデバイス間のデータ転送は、C++ クラスまたは使い慣れた動的メモリー割り当て構文を使って行われます。

SYCL* は、アクセラレーター・デバイスをサポートする拡張を備えた C++ であり、C++ テンプレート・ライブラリーの新しいセットを追加するだけです (図 1)。これは、一般的な SAXPY (単精度の A×X+Y) 計算を C++ で実装した例 (左) と SYCL* で実装した例 (右) を並べるとよく分かります。

2 つのコードはよく似ています。C++ コードは、SAXPY 計算をアクセラレーターにオフロードするように変更されています。

  • 行 1:SYCL* ヘッダーをインクルードします。
  • 行 8:デフォルトのアクセラレーター用の SYCL* キューを作成します。アクセラレーターが利用できない場合、キューにサブミットされたワークはホストで実行されます。
  • 行 9–11:ワーク配列を USM に割り当てます。SYCL* ランタイムは、ホストとキュー・オブジェクトで指定されたアクセラレーターの間のデータ転送を処理します。
  • 行 19:SAXPY 計算をキューの引数で指定したアクセラレーター上で並列に実行する C++ ラムダ関数としてサブミットします。
  • 行 23:ヘテロジニアス並列プログラミングに対する一部の独自アプローチとは異なり、SYCL* は非同期のイベントベースの言語です。SYCL* キューにワークをサブミットしても、ホスト (またはほかのアクセラレーター) が有用なワークを続行するのを妨げることはありません。wait() メソッドは、アクセラレーターが SAXPY 計算を完了する前にホストが終了するのを防ぎます。

oneAPI は生産的な高速化アプローチであると前述しましたが、それを証明する例を示します。STL は C++ プログラマーの生産性を大幅に向上します。oneAPI データ並列 C++ ライブラリー(oneDPL) は、C++ STL の oneAPI により高速化された実装です。次の例は、C++ STL (左) と oneDPL (右) を使って maxloc リダクションを実行します。

1 C++ コンパイラーはこの 20年で大幅に改善されたので、もはやこれは真実ではありません。
2 「C++ vs. Rust」で検索すると、議論を見ることができます。
3 「フリーランチ」とは、周波数スケーリングからコア・スケーリングへの移行を指します。「ハードウェア・ジャングル」とは、専用プロセッサーの普及を指します。
4 SYCL* は、画像データに特化したクラスも提供していますが、画像の取り扱いは本記事の範囲外です。

タイトルとURLをコピーしました