DPC++ の使用方法を示します。
次の手順に従って、アプリケーションに Parallel STL を追加します。
C++ 標準ライブラリーとの共存性を高めるには、標準 C++ ヘッダーファイルの前にインテル® oneDPL ヘッダーファイルをインクルードします。
#include <oneapi/dpl/execution> #include <oneapi/dpl/algorithm> #include <vector> int main() { std::vector<int> data( 1000 ); std::fill(oneapi::dpl::execution::par_unseq, data.begin(), data.end(), 42); return 0; }
DPC++ 実行ポリシーは、並列アルゴリズムを実行する場所と方法を定義します。SYCL* デバイスまたはキューをカプセル化し、オプションのカーネル名を設定することができます。DPC++ 実行ポリシーは、実行ポリシーをサポートするすべての標準 C++ アルゴリズムで使用できます。
ポリシーを使用するには、一意のカーネル名のクラス型を示すテンプレート引数と次のいずれかのコンストラクター引数を指定してポリシー・オブジェクトを作成します。
使用されるコンパイラーが SYCL* カーネル関数の暗黙名をサポートしている場合、ポリシーのカーネル名の指定はオプションです。インテル® oneAPI DPC++/C++ コンパイラーはデフォルトでサポートしています。ほかのコンパイラーでは、-fsycl-unnamed-lambda などのコンパイラー・オプションを使用して有効にする必要があります。詳細は、コンパイラーのドキュメントを参照してください。
oneapi::dpl::execution::dpcpp_default オブジェクトは、device_policy クラスの事前定義オブジェクトです。デフォルトのカーネル名とデフォルトのキューで作成されています。カスタマイズされたポリシー・オブジェクトの作成に使用したり、アルゴリズムを呼び出す際に直接渡すことができます。
dpcpp_default が複数のアルゴリズムに直接渡される場合、コンパイル時に暗黙のカーネル名を有効にする必要があります (上記参照)。
make_device_policy 関数テンプレートは、device_policy の作成を簡素化します。
以下のコード例では、ポリシークラスと関数を参照する際に、名前空間 oneapi::dpl::execution; と sycl; ディレクティブを使用することを仮定しています。
auto policy_a = device_policy<class PolicyA> {}; std::for_each(policy_a, …);
auto policy_b = device_policy<class PolicyB> {device{gpu_selector{}}}; std::for_each(policy_b, …);
auto policy_c = device_policy<class PolicyC> {cpu_selector{}}; std::for_each(policy_c, …);
auto policy_d = make_device_policy<class PolicyD>(dpcpp_default); std::for_each(policy_d, …);
auto policy_e = make_device_policy(queue{property::queue::in_order()}); std::for_each(policy_e, …);
fpga_policy クラスは、FPGA ハードウェア・デバイス上で並列アルゴリズムのパフォーマンスを向上するように調整された DPC++ ポリシーのです。
FPGA ハードウェア・デバイスや FPGA エミュレーション・デバイスでアプリケーションを実行する場合に使用します。
fpga_policy のデフォルトのコンストラクターは、ONEDPL_FPGA_EMULATOR が定義されている場合、fpga_selector または fpga_emulator_selector 用に構築された SYCL* キューを持つオブジェクトを作成します。
oneapi::dpl::execution::dpcpp_fpga は、デフォルトのアンロール係数とデフォルトのカーネル名で作成された fpga_policy クラスの定義済みオブジェクトです。カスタマイズされたポリシー・オブジェクトの作成に使用したり、アルゴリズムを呼び出す際に直接渡すことができます。
make_fpga_policy 関数テンプレートは、fpga_policy の作成を簡素化します。
以下のコード例では、ポリシーには名前空間 oneapi::dpl::execution; を、キューとデバイスセレクターには sycl; を使用することを仮定しています。
constexpr auto unroll_factor = 8; auto fpga_policy_a = fpga_policy<unroll_factor, class FPGAPolicyA>{}; auto fpga_policy_b = make_fpga_policy(queue{intel::fpga_selector{}}); auto fpga_policy_c = make_fpga_policy<unroll_factor, class FPGAPolicyC>();
次のいずれかの方法で、DPC++ ポリシーを使用して実行されるアルゴリズムにデータを渡すことができます。
oneapi::dpl::begin と oneapi::dpl::end 関数の使用
oneapi::dpl::begin と oneapi::dpl::end は、SYCL* バッファーを Parallel STL アルゴリズムに渡すことができるヘルパー関数です。これらの関数は、SYCL* バッファーを受け取り、以下の要件を満たす不特定型のオブジェクトを返します。
これらの関数を使用するには、コードに #include <oneapi/dpl/iterator> を追加します。
例:
#include <oneapi/dpl/execution> #include <oneapi/dpl/algorithm> #include <oneapi/dpl/iterator> #include <CL/sycl.hpp> int main(){ sycl::buffer<int> buf { 1000 }; auto buf_begin = oneapi::dpl::begin(buf); auto buf_end = oneapi::dpl::end(buf); std::fill(oneapi::dpl::execution::dpcpp_default, buf_begin, buf_end, 42); return 0; }
統合共有メモリー (USM) の使用
以下の例は、Parallel STL アルゴリズムと USM を併用する 2 つの方法を示します。
USM に割り当てられたバッファーがある場合は、バッファーの開始 (先頭) と終了 (最後の後) へのポインターを並列アルゴリズムに渡します。実行ポリシーとバッファーが同じキューに対して作成されていることを確認します。
#include <oneapi/dpl/execution> #include <oneapi/dpl/algorithm> #include <CL/sycl.hpp> int main(){ sycl::queue q; const int n = 1000; int* d_head = sycl::malloc_device<int>(n, q); std::fill(oneapi::dpl::execution::make_device_policy(q), d_head, d_head + n, 42); sycl::free(d_head, q); return 0; }
あるいは、std::vector と USM アロケーターを併用できます。
#include <oneapi/dpl/execution> #include <oneapi/dpl/algorithm> #include <CL/sycl.hpp> int main(){ const int n = 1000; auto policy = oneapi::dpl::execution::dpcpp_default; sycl::usm_allocator<int, sycl::usm::alloc::shared> alloc(policy.queue()); std::vector<int, decltype(alloc)> vec(n, alloc); std::fill(policy, vec.begin(), vec.end(), 42); return 0; }
ホスト側の std::vector の使用
インテル® oneDPL 並列アルゴリズムは、以下のコード例に示すように、通常の (ホスト側の) イテレーターを使用して呼び出すことができます。この場合、一時的な SYCL* バッファーが作成され、このバッファーにデータがコピーされます。デバイス上で一時バッファーの処理が完了すると、データはホストにコピーバックされます。ホストとデバイス間のデータコピーを軽減するため、SYCL* バッファーを使用することを推奨します。次に例を示します。
#include <oneapi/dpl/execution> #include <oneapi/dpl/algorithm> #include <vector> int main(){ std::vector<int> v( 1000 ); std::fill(oneapi::dpl::execution::dpcpp_default, v.begin(), v.end(), 42); // each element of vec equals to 42 return 0; }
DPC++ のエラー処理モデルは、2 種類のエラーをサポートしています。同期エラーの場合、DPC++ ホスト・ランタイム・ライブラリーは例外をスローしますが、非同期エラーは DPC++ キューに関連付けられたユーザー提供のエラーハンドラーでのみ処理されます。
DPC++ ポリシーで実行されるアルゴリズムでは、同期または非同期にかかわらず、すべてのエラーを処理するのは呼び出し元の責任です。
DPC++ 非同期エラーを処理するためには、エラー・ハンドラー・オブジェクトで DPC++ ポリシーに関連付けられたキューを作成する必要があります。定義済みポリシー・オブジェクト (dpcpp_default など) にはエラーハンドラーがないため、非同期エラーを処理する必要がある場合は使用しないでください。
DPC++ 実行ポリシーで使用される場合、インテル® oneDPL アルゴリズムは、以下のような DPC++ と同じ制限を適用します (詳細は DPC++ 仕様と SYCL* 仕様を参照)。
transform_exclusive_scan と transform_inclusive_scan アルゴリズムでは、単項演算の結果は、初期値が提供されている場合は初期値の型に、そうでない場合は処理されたデータシーケンスの値の型に変換可能でなければなりません (std::iterator_traits<IteratorType>::value_type)。
以下の手順に従って、インテル® oneDPL でコードをビルドします。
以下は、Linux*上でインテル® oneDPL 並列アルゴリズムを含むコードをコンパイルするためのコマンドラインの例です (コードによっては [] 内のパラメーターは不要な場合があります)。
dpcpp [–fsycl-unnamed-lambda] test.cpp [-ltbb] -o test