この記事は、The Parallel Universe Magazine 51 号に掲載されている「Migrate C++ Thrust Applications to SYCL and oneDPL」の日本語参考訳です。原文は更新される可能性があります。原文と翻訳文の内容が異なる場合は原文を優先してください。
プログラマーはライブラリーが大好きです。単に怠け者だからというだけでなく、ライブラリーが生産性とパフォーマンスを向上するからです。よくあるパターンを最適化するのに時間を浪費し、ターゲット・ハードウェアが変更されるたびにまた同じことをするのは無意味です。パターンを特定する作業はすでに終わっているため、ISO C++ 標準テンプレート・ライブラリー (STL) のような事前定義の抽象化機能を使うべきです。これらはすぐに使えて、専門家によって最適化されています。
STL は CPU だけをターゲットにしているのであれば最適ですが、コンピューティングの世界はヘテロジニアスです。CPU だけでなく、それ以外の選択肢も必要です。NVIDIA* Thrust ライブラリーと oneAPI データ並列 C++ ライブラリー (oneDPL) は CPU 以外にも対応しています。この 2 つを比較してみましょう。どちらもオープンソース・プロジェクト1、2ですが、Thrust のデバイスサポートはプロプライエタリーなソフトウェア・コンポーネントに依存しています (CUB*/CUDA* を通じてのみ GPU をサポートしています)。つまり、ベンダー依存です。一方、oneDPL は SYCL* をベースにしており、複数のベンダーのアクセラレーターをサポートするように設計されています。3
Thrust からベンダーに依存しない oneDPL への移行を検討するのは当然と言えます。ここでは、2 つの簡単な例を使用して、ツールを使用するアプローチと手動でコーディングし直すアプローチの 2つの補完的な移行戦略について見ていきます。CUDA* を SYCL* に移行する SYCLomatic ツールについては、皆さんすでにご存知かもしれません。4ツールを使用した移行には限界があり、特にテンプレート化されたコードでは、必ずしも機能的ではなく、生成されるコードの品質や保守性には限界があります。例えば、ラムダ関数や自動型推論のような新しいC++ の機能を導入する場合、手作業による編集やコードの現代化が通常必要になります。移行後のコードは、オープン・スタンダードに準拠した、最新の C++ に沿ったものになります。Thrust は登場から 10年が経っており、そろそろコードをアップグレードする時期と言えます。いくつかのコード例を見てみましょう。
最初の例は、シーケンス中のいくつかの値をインプレース変換します。変換する値はマスクで定義します。この関数は、値を格納するストリームと、ステンシルシーケンスを格納するストリームの 2 つの入力ストリームを受け取ります。最初のシーケンスの各値は、2 番目の入力シーケンスのステンシル値が特定の条件 (コード例では非ゼロのプレディケート) を満たす場合に変換 (コード例では符号反転) されます。
// Thrust コード例 (ステージ 0) thrust::transform_if(dev_inp, dev_inp + 10, dev_stencil, dev_inp, thrust::negate<int>(), thrust::identity<int>());
oneDPL は標準 C++ アルゴリズムと密接に連携しています。STL はステンシルのオーバーロードをサポートしていないため、oneDPL も同様にサポートしていません。このパターンは別の方法で表現する必要があるため、SYCLomatic は、ドロップイン置換として代替案を提供しています。
// 移行後のコード (ステージ 1) dpct::transform_if(oneapi::dpl::execution::make_device_policy(syclQueue), dev_inp, dev_inp + 10, dev_stencil, dev_inp, std::negate<int>(), dpl::identity<int>()); // identity は C++20 の機能
ラムダ関数をカスタム・ファンクターとして使用し、標準 C++ コードを作成することで、さらに改善できます。この方法では、ステンシルシーケンスが一般的な入力シーケンスとは異なる方法で処理されます。プレディケートの評価はカスタム・ファンクターの内部で行われ、ライブラリーの内部に隠されることはありません。
// 手動編集後 (ステージ 2) dpl::transform(dpl::execution::dpcpp_default, dev_inp, dev_inp + 10, dev_stencil, dev_inp, [&](const auto& input, const auto& mask) { return mask ? std::negate<>()(input) : input; });
1 oneDPL リポジトリー: https://github.com/oneapi-src/oneDPL (英語)
2 Thrust リポジトリー: https://github.com/NVIDIA/thrust (英語)
3 「SYCL* の事例: ISO C++ がヘテロジニアス・コンピューティングに十分でない理由」