データ並列 c++ (インテル® oneapi dpc++) の紹介

44
Mike Kinsner インテル コーポレーション ソフトウェア・エンジニア Khronos* SYCL* および OpenCL* ワーキンググループのメンバー データ並列 C++ (インテル® oneAPI DPC++) の紹介 標準ベースのクロスアーキテクチャー プログラミング言語

Upload: others

Post on 19-May-2022

7 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: データ並列 C++ (インテル® oneAPI DPC++) の紹介

Mike Kinsner

インテル コーポレーションソフトウェア・エンジニア

Khronos* SYCL* および OpenCL* ワーキンググループのメンバー

データ並列 C++ (インテル® oneAPI DPC++) の紹介標準ベースのクロスアーキテクチャープログラミング言語

Page 2: データ並列 C++ (インテル® oneAPI DPC++) の紹介

目的

データ並列 C++、コードの構造、およびコードをすぐに記述するための主要概念を紹介

Page 3: データ並列 C++ (インテル® oneAPI DPC++) の紹介

トピック

1. データ並列 C++ とは?

2. プログラミング構造と実行モデル

3. コードのコンパイル

4. キューとデバイスの選択

5. データとタスクグラフの管理

6. インテルによる DPC++ の拡張

Page 4: データ並列 C++ (インテル® oneAPI DPC++) の紹介

プログラミング課題

データセントリックのハードウェアの多様性

共通のプログラミング言語や API はありません

プラットフォーム間でのツールサポートの一貫性がありません

それぞれのプラットフォームには個別のソフトウェア投資が必要です

FPGAAIGPUCPU

Page 5: データ並列 C++ (インテル® oneAPI DPC++) の紹介

いくつかの機能はアーキテクチャーごとに異なります

AI(将来)

FPGAGPUCPU

最適化されたアプリケーション

最適化されたミドルウェアとフレームワーク

インテル® oneAPI 製品

ダイレクト・プログラミング

データ並列 C++

API ベースのプログラミング

ライブラリー

解析、デバッグツール

インテル® oneAPI はクロスアーキテクチャー向けのパフォーマンスをもたらします

すぐに動作します、 そして解析とチューニングを行います

Page 6: データ並列 C++ (インテル® oneAPI DPC++) の紹介

データ並列 C++

= 標準化された C++ と SYCL* および インテルによる拡張

C++ ベース

▪C++ の生産性の利点と慣れ親しんだ構文

標準化ベースのクロスアーキテクチャー

▪データ並列処理とヘテロジニアス・プログラミング向けに SYCL* 標準を取り入れています

データ並列 C++ ⇔ DPC++

データ並列 C++ とは?

Khronos は登録商法であり、SYCL は Khronos Group, Inc の商標です。

Page 7: データ並列 C++ (インテル® oneAPI DPC++) の紹介

生産性の向上

• シンプルなものはシンプルに表現する必要があります

• 冗長性とプログラマーの負担を軽減します

パフォーマンスの向上

• プログラマーがプログラムの実行を制御できるようにします

• ハードウェア固有の機能を有効にします

DPC++: SYCL* 規格への迅速でオープンな実装

• 上流の LLVM を目標とするオープンソース実装

• DPC++ 拡張は、SYCL* コアや Khronos* 拡張に取り入れられることを目的としています

DPC++ は SYCL* 1.2.1 を拡張

Page 8: データ並列 C++ (インテル® oneAPI DPC++) の紹介

完全な DPC++ プログラム#include <CL/sycl.hpp>#include <iostream>constexpr int num=16;using namespace cl::sycl;

int main() {auto R = range<1>{ num };buffer<int> A{ R };

queue{}.submit([&](handler& h) {auto out =A.get_access<access::mode::write>(h);

h.parallel_for(R, [=](id<1> idx) {out[idx] = idx[0]; }); });

auto result =A.get_access<access::mode::read>();

for (int i=0; i<num; ++i)std::cout << result[i] << "¥n";

return 0;}

単一ソース

• ホストコードとヘテロジニアス・アクセラレーター向けのカーネルを混在して同一ソースファイルに記述できます

C++ のように利用できます

• ライブラリー構造は、次のような機能を追加します

ホストコード

アクセラレーター・デバイス・コード

ホストコード

構造 目的

キュー ターゲットでのワーク

バッファー データ管理

parallel_for 並列処理

Page 9: データ並列 C++ (インテル® oneAPI DPC++) の紹介

インテル® DPC++ コンパイラーを使用

• dpcpp -fsycl-unnamed-lambda my_source.cpp –o executable

コンパイラーを入手

DPC++ プログラムのコンパイル

oneAPI 向けのインテル® DevCloud

(software.intel.com/devcloud/oneAPI (英語)) でインテル® oneAPI を使用して、さまざまなインテルの

データセントリックなアーキテクチャーでコードとワークロードをテストできます

関連情報とダウンロードインテル® oneAPI ツールキットは、software.intel.com/oneapi (英語)

からダウンロードできます

Page 10: データ並列 C++ (インテル® oneAPI DPC++) の紹介

ワークロードの評価

ヘテロジニアス・アプリケーションのビルド

インテル® oneAPI ツールキットを学習

データ並列 C++ を学習

プロジェクトのプロトタイプを作成

ダウンロード不要 | ハードウェア不要 | インストール不要 | セットアップと設定不要

わずかな時間で起動および実行できます!!

インテル® oneAPI ツールキットを使用して、さまざまなインテルの CPU、GPU、FPGA でワークロードを開発、テスト、および実行する開発向けのサンドボックス

インテル® oneAPI を使用する

インテル® DevCloud

software.intel.com/en-us/devcloud/oneapi (英語)

Page 11: データ並列 C++ (インテル® oneAPI DPC++) の紹介

アクセラレーター・デバイスで実行するワークを定義

#include <CL/sycl.hpp>#include <iostream>constexpr int num=16;using namespace cl::sycl;

int main() {auto R = range<1>{ num };buffer<int> A{ R };

queue{}.submit([&](handler& h) {auto out = A.get_access<access::mode::write>(h);h.parallel_for(R, [=](id<1> idx) {out[idx] = idx[0]; }); });

auto result = A.get_access<access::mode::read>();for (int i=0; i<num; ++i)std::cout << result[i] << "¥n";

return 0;}

次の方法でカーネルを指定できます:

• C++ ラムダ式

• 関数を呼び出す軽量のラッパー

• ファンクター・オブジェクト

• 相互運用

カーネルはキューに送信されます:

• parallel_for

• single_task

• parallel_for_work_group

カーネルのラムダ式定義

Page 12: データ並列 C++ (インテル® oneAPI DPC++) の紹介

カーネルベースのモデル#include <CL/sycl.hpp>#include <iostream>constexpr int num=16;using namespace cl::sycl;

int main() {auto R = range<1>{ num };buffer<int> A{ R };

queue{}.submit([&](handler& h) {auto out =A.get_access<access::mode::write>(h);

h.parallel_for(R, [=](id<1> idx) {out[idx] = idx[0]; }); });

auto result =A.get_access<access::mode::read>();

for (int i=0; i<num; ++i)std::cout << result[i] << "¥n";

return 0;}

カーネル

• アクセラレーターで実行されるコード(通常、カーネル呼び出しごとに (ND-range 全体で) 繰り返し呼び出されるインスタンス)

カーネルはコード中で明確に識別できます

• 少数のクラスでカーネルを定義できます (例: parallel_for)

開発者はカーネルを実行する位置を指定します

• 各種レベルの制御

カーネルの定義

Page 13: データ並列 C++ (インテル® oneAPI DPC++) の紹介

データ並列処理は ND-range を使用して表現されます

• 合計ワーク = Work-group 数 x Work-group ごとの Work-item 数

• ボトムアップ、階層型の単一プログラム複数データ (SPMD) モデル

カーネルの実行モデル

ND-range の次元 2

ND-range の次元 1

ND-range の次元 0

work-item (4,4,4) の work-group

ND-range

sub-group の次元 0

Sub-group Work-itemWork-group

4 つの work-item の sub-group

work-group の次元 2

work-group の次元 1

work-group の次元 0

単一の work-item の視点からコードを記述

集合関数により、work-item 間の操作を表現可能 (バリアなど)

Page 14: データ並列 C++ (インテル® oneAPI DPC++) の紹介

ワークはキューへ送信されます

• 各キューは、厳密に 1 つのデバイスに関連付けられます (例: GPU または FPGA)

• 次を行うことができます:

• キューに関連付けられているデバイスを特定 (必要であれば)

• ヘテロジニアス・システムでは、ワークをディスパッチする複数のキューを作成

デバイスカーネルを実行する場所を選択

対象のデバイス向けのキューを作成 queue();

事前構成されているデバイスクラスをターゲットとするキューを作成

queue(cpu_selector{});

queue(gpu_selector{});

queue(intel::fpga_selector{});

queue(accelerator_selector{});

queue(host_selector{});

特定のデバイスをターゲットとするキューを作成 (特殊用途):

class custom_selector : public

device_selector {

int operator()(…… // ロジックを記述 !…

queue(custom_selector{});

Queue_A

Queue_B

Queue_C

GPU

FPGA

常に利用可能

Page 15: データ並列 C++ (インテル® oneAPI DPC++) の紹介

自動でデータと制御の依存関係を解決します

カーネル実行のグラフ

プログラムの完了

A

A

B

A

B

int main() {auto R = range<1>{ num };buffer<int> A{ R }, B{ R };queue Q;

Q.submit([&](handler& h) {auto out = A.get_access<access::mode::write>(h);h.parallel_for(R, [=](id<1> idx) {out[idx] = idx[0]; }); });

Q.submit([&](handler& h) {auto out = A.get_access<access::mode::write>(h);h.parallel_for(R, [=](id<1> idx) {out[idx] = idx[0]; }); });

Q.submit([&](handler& h) {auto out = B.get_access<access::mode::write>(h);h.parallel_for(R, [=](id<1> idx) {out[idx] = idx[0]; }); });

Q.submit([&](handler& h) {auto in = A.get_access<access::mode::read>(h);auto inout =B.get_access<access::mode::read_write>(h);

h.parallel_for(R, [=](id<1> idx) {inout[idx] *= in[idx]; }); });

}

カーネル 1

カーネル 3

カーネル 2

カーネル 4

= データ依存関係

カーネル 1

カーネル 2

カーネル 3

カーネル 4

Page 16: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファー: SYCL* アプリケーションでデータをカプセル化

• デバイスとホスト間の両方で

アクセサー: バッファーのデータにアクセスするメカニズム

• カーネルの実行を順序付けるSYCL* グラフでデータの依存関係を作成

バッファーモデル

int main() {auto R = range<1>{ num };buffer<int> A{ R }, B{ R };queue Q;

Q.submit([&](handler& h) {auto out =

A.get_access<access::mode::write>(h);h.parallel_for(R, [=](id<1> idx) {

out[idx] = idx[0]; }); });

Q.submit([&](handler& h) {auto out =

A.get_access<access::mode::write>(h);h.parallel_for(R, [=](id<1> idx) {

out[idx] = idx[0]; }); });…

A

AB

A

Bカーネル1

カーネル3

カーネル2

カーネル4

バッファー

バッファーへのアクセサー

Page 17: データ並列 C++ (インテル® oneAPI DPC++) の紹介

SYCL* アプリケーションは 2 つのパートで構成されます

1. ホストコード

2. カーネル実行のグラフ

同期操作を除きこれらは独立して実行されます

• ホストコードはグラフを作成するためワーク (計算を行う) を送信します

• カーネル実行とデータ移動のグラフは、SYCL* ランタイムで管理されるホストコードとは非同期に実行されます

非同期実行

Page 18: データ並列 C++ (インテル® oneAPI DPC++) の紹介

#include <CL/sycl.hpp>#include <iostream>constexpr int num=16;using namespace cl::sycl;

int main() {auto R = range<1>{ num };buffer<int> A{ R };

queue{}.submit([&](handler& h) {auto out = A.get_access<access::mode::write>(h);h.parallel_for(R, [=](id<1> idx) {out[idx] = idx[0]; }); });

auto result = A.get_access<access::mode::read>();for (int i=0; i<num; ++i)std::cout << result[i] << "¥n";

return 0;}

ホストコードの実行

カーネルをキューに投入してグラフ化し処理を続行します

カーネル

A

A

グラフはホスト・プログラムとは非同期に実行されます

ホスト グラフ

非同期実行 (続き)

Page 19: データ並列 C++ (インテル® oneAPI DPC++) の紹介

注意:

DPC++ = ISO C++ と SYCL* 標準 および 拡張機能

SYCL* 上の DPC++ 拡張機能

次の機能を見ていきます

1. 統合共有メモリー

2. Sub-group

3. 順序付けされたキュー

4. パイプ

5. オプションのラムダ命名

Page 20: データ並列 C++ (インテル® oneAPI DPC++) の紹介

SYCL* 1.2.1 標準はバッファーメモリーを抽象化します

• 強力でエレガントに表現されたデータ依存関係

しかし …

• すべてのポインターと配列を C++ プログラムでバッファーに置き換えるのは大きな負担です

DPC++ の USM はポインターベースの代替手段を提供します

• アクセラレーターへの移植を容易にします

• プログラマーに必要な制御レベルを提供します

• バッファーを補完します

統合共有メモリー (USM)

Page 21: データ並列 C++ (インテル® oneAPI DPC++) の紹介

USM の割り当てとポインターの使用法

タイプ 説明 アクセス可能 制御対象

デバイスデバイス割り当て

ホスト ホスト

デバイス ✓ デバイス

その他のデバイス

? その他のデバイス

ホストホスト割り当て

ホスト ✓ ホスト

任意のデバイス

✓ デバイス

共有潜在的な移行の割り当て

ホスト ✓ ホスト ✓*

デバイス ✓ デバイス ✓*

その他のデバイス

? その他のデバイス

?

auto A = (int*)malloc_shared(N*sizeof(int), …);auto B = (int*)malloc_shared(N*sizeof(int), …);…

q.submit([&](handler& h) {h.parallel_for(range<1>{N}, [=] (id<1> ID) {auto i = ID[0];A[i] *= B[i];

});});

アロケーション・タイプ

自動データアクセスと明示的なデータ移動がサポートされます

Page 22: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファーは強力でエレガント

• アプリケーションでうまく抽象化されている場合、またはバッファーが開発に影響しない場合に使用します

USM は使い慣れたポインターベースの C++ インターフェイスを提供

• 最小限の修正で C++ コードを DPC++ へ移植できます

• コードを移植する際に共有割り当てを使用して即座に機能させます

USM ではすぐに最高のパフォーマンスが得られるわけではありません

• すぐに機能させることができます

• プロファイラーを使用して、チューニングの必要があるコード領域を特定します

統合共有メモリー - いつ使用するか

Page 23: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファーの例 - 共通パターン

auto A = (int *) malloc(N * sizeof(int));auto B = (int *) malloc(N * sizeof(int));auto C = (int *) malloc(N * sizeof(int));

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

{buffer<int, 1> Ab(A, range<1>{N});buffer<int, 1> Bb(B, range<1>{N});buffer<int, 1> Cb(C, range<1>{N});

q.submit([&] (handler& h) {auto R = range<1>{N};auto aA = Ab.get_access<access::mode::read>(h);auto aB = Bb.get_access<access::mode::read>(h);auto aC = Cb.get_access<access::mode::write>(h);h.parallel_for(R, [=] (id<1> i) {

aC[i] = aA[i] + aB[i];});

});} // A、B、C 更新

C++ 配列の宣言

Page 24: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファーの例 - 共通パターン

auto A = (int *) malloc(N * sizeof(int));auto B = (int *) malloc(N * sizeof(int));auto C = (int *) malloc(N * sizeof(int));

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

{buffer<int, 1> Ab(A, range<1>{N});buffer<int, 1> Bb(B, range<1>{N});buffer<int, 1> Cb(C, range<1>{N});

q.submit([&] (handler& h) {auto R = range<1>{N};auto aA = Ab.get_access<access::mode::read>(h);auto aB = Bb.get_access<access::mode::read>(h);auto aC = Cb.get_access<access::mode::write>(h);h.parallel_for(R, [=] (id<1> i) {

aC[i] = aA[i] + aB[i];});

});} // A、B、C 更新

C++ 配列の宣言

C ++ 配列の初期化

Page 25: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファーの例 - 共通パターン

auto A = (int *) malloc(N * sizeof(int));auto B = (int *) malloc(N * sizeof(int));auto C = (int *) malloc(N * sizeof(int));

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

{buffer<int, 1> Ab(A, range<1>{N});buffer<int, 1> Bb(B, range<1>{N});buffer<int, 1> Cb(C, range<1>{N});

q.submit([&] (handler& h) {auto R = range<1>{N};auto aA = Ab.get_access<access::mode::read>(h);auto aB = Bb.get_access<access::mode::read>(h);auto aC = Cb.get_access<access::mode::write>(h);h.parallel_for(R, [=] (id<1> i) {

aC[i] = aA[i] + aB[i];});

});} // A、B、C 更新

C++ 配列の宣言

C ++ 配列の初期化

バッファーの宣言

Page 26: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファーの例 - 共通パターン

auto A = (int *) malloc(N * sizeof(int));auto B = (int *) malloc(N * sizeof(int));auto C = (int *) malloc(N * sizeof(int));

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

{buffer<int, 1> Ab(A, range<1>{N});buffer<int, 1> Bb(B, range<1>{N});buffer<int, 1> Cb(C, range<1>{N});

q.submit([&] (handler& h) {auto R = range<1>{N};auto aA = Ab.get_access<access::mode::read>(h);auto aB = Bb.get_access<access::mode::read>(h);auto aC = Cb.get_access<access::mode::write>(h);h.parallel_for(R, [=] (id<1> i) {

aC[i] = aA[i] + aB[i];});

});} // A、B、C 更新

C ++ 配列の作成

C ++ 配列の初期化

バッファーの作成

アクセサーの作成

Page 27: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファーの例 - 共通パターン

auto A = (int *) malloc(N * sizeof(int));auto B = (int *) malloc(N * sizeof(int));auto C = (int *) malloc(N * sizeof(int));

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

{buffer<int, 1> Ab(A, range<1>{N});buffer<int, 1> Bb(B, range<1>{N});buffer<int, 1> Cb(C, range<1>{N});

q.submit([&] (handler& h) {auto R = range<1>{N};auto aA = Ab.get_access<access::mode::read>(h);auto aB = Bb.get_access<access::mode::read>(h);auto aC = Cb.get_access<access::mode::write>(h);h.parallel_for(R, [=] (id<1> i) {

aC[i] = aA[i] + aB[i];});

});} // A、B、C 更新

C ++ 配列の作成

C ++ 配列の初期化

バッファーの作成

アクセサーの作成

カーネルでアクセサーを使用

Page 28: データ並列 C++ (インテル® oneAPI DPC++) の紹介

バッファーの例 - 共通パターン

auto A = (int *) malloc(N * sizeof(int));auto B = (int *) malloc(N * sizeof(int));auto C = (int *) malloc(N * sizeof(int));

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

{buffer<int, 1> Ab(A, range<1>{N});buffer<int, 1> Bb(B, range<1>{N});buffer<int, 1> Cb(C, range<1>{N});

q.submit([&] (handler& h) {auto R = range<1>{N};auto aA = Ab.get_access<access::mode::read>(h);auto aB = Bb.get_access<access::mode::read>(h);auto aC = Cb.get_access<access::mode::write>(h);h.parallel_for(R, [=] (id<1> i) {

aC[i] = aA[i] + aB[i];});

});} // A、B、C 更新

C ++ 配列の作成

C ++ 配列の初期化

バッファーの作成

アクセサーの作成

カーネルでアクセサーを使用

C++ 配列の更新

Page 29: データ並列 C++ (インテル® oneAPI DPC++) の紹介

USM の例 - 共通パターン

USM 配列の宣言auto A = (int *) malloc_shared(N * sizeof(int), …);auto B = (int *) malloc_shared(N * sizeof(int), …);auto C = (int *) malloc_shared(N * sizeof(int), …);

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

q.submit([&] (handler& h) {auto R = range<1>{N};h.parallel_for(R, [=] (id<1> ID) {auto i = ID[0];C[i] = A[i] + B[i];

});});q.wait();// A、B、C を更新し利用できるようにする

Page 30: データ並列 C++ (インテル® oneAPI DPC++) の紹介

USM の例 - 共通パターン

auto A = (int *) malloc_shared(N * sizeof(int), …);auto B = (int *) malloc_shared(N * sizeof(int), …);auto C = (int *) malloc_shared(N * sizeof(int), …);

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

q.submit([&] (handler& h) {auto R = range<1>{N};h.parallel_for(R, [=] (id<1> ID) {auto i = ID[0];C[i] = A[i] + B[i];

});});q.wait();// A、B、C を更新し利用できるようにする

USM 配列の宣言

USM 配列の初期化

Page 31: データ並列 C++ (インテル® oneAPI DPC++) の紹介

USM の例 - 共通パターン

auto A = (int *) malloc_shared(N * sizeof(int), …);auto B = (int *) malloc_shared(N * sizeof(int), …);auto C = (int *) malloc_shared(N * sizeof(int), …);

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

q.submit([&] (handler& h) {auto R = range<1>{N};h.parallel_for(R, [=] (id<1> ID) {auto i = ID[0];C[i] = A[i] + B[i];

});});q.wait();// A、B、C を更新し利用できるようにする

USM 配列の宣言

USM 配列の初期化

USM 配列を直接読み書き

ポインターを使用 !

Page 32: データ並列 C++ (インテル® oneAPI DPC++) の紹介

USM の例 - 共通パターン

auto A = (int *) malloc_shared(N * sizeof(int), …);auto B = (int *) malloc_shared(N * sizeof(int), …);auto C = (int *) malloc_shared(N * sizeof(int), …);

for (int i = 0; i < N; i++) {A[i] = i; B[i] = 2*i;

}

q.submit([&] (handler& h) {auto R = range<1>{N};h.parallel_for(R, [=] (id<1> ID) {auto i = ID[0];C[i] = A[i] + B[i];

});});q.wait();// A、B、C を更新し利用できるようにする

USM 配列の宣言

USM 配列の初期化

USM 配列を直接読み書き

USM 配列の更新

Page 33: データ並列 C++ (インテル® oneAPI DPC++) の紹介

USM とタスク・スケジュール (オプション)

明示的なスケジュール

• カーネルを送信するとイベントが返ります

• イベントを待機してタスクを実行します

auto E = q.submit([&] (handler& h) {auto R = range<1>{N};h.parallel_for(R, [=] (id<1> ID) {auto i = ID[0];C[i] = A[i] + B[i];

});});E.wait();

auto R = range<1>{N};

auto E = q.submit([&] (handler& h) {h.parallel_for(R, [=] (id<1> ID) {…});

});

q.submit([&] (handler& h) {h.depends_on(E);h.parallel_for(R, [=] (id<1> ID) {…});

});

DPC++ グラフ・スケジュール

• イベントからタスクのエッジを構築

Page 34: データ並列 C++ (インテル® oneAPI DPC++) の紹介

work-item のグループ化

• ベクトル/SIMD ハードウェアにマッピング可能

• 集合操作を明示 (シャッフル、バリア、レデュースなど)

Sub-group

work-group を Sub-group へ分解

グローバル・ワーク・サイズ

Work-group

Work-item

Sub-group

Page 35: データ並列 C++ (インテル® oneAPI DPC++) の紹介

順序付けされたキュー

DPC++ のキューはアウトオブオーダー

• 複雑な DAG を表現できます

線形タスクチェーンは一般的です

• ここでは DAG は不要であり、冗長性につながります

シンプルなものはシンプルに表現する必要があります

• インオーダーのセマンティクスは線形タスクパターンを容易に表現できます

// 順序付けされたキューなしqueue q;auto R = range<1>{N};

auto E = q.submit([&] (handler& h) {h.parallel_for(R, [=] (id<1> ID) {…});

});

auto F = q.submit([&] (handler& h) {h.depends_on(E);h.parallel_for(R, [=] (id<1> ID) {…});

});

q.submit([&] (handler& h) {h.depends_on(F);h.parallel_for(R, [=] (id<1> ID) {…});

});

Page 36: データ並列 C++ (インテル® oneAPI DPC++) の紹介

順序付けされたキュー (続き)

DPC++ のキューはアウトオブオーダー

• 複雑な DAG を表現できます

線形タスクチェーンは一般的です

• ここでは DAG は不要であり、冗長性につながります

シンプルなものはシンプルに表現する必要があります

• インオーダーのセマンティクスは線形タスクパターンを容易に表現できます

// 順序付けされたキューordered_queue q;auto R = range<1>{N};

q.submit([&] (handler& h) {h.parallel_for(R, [=] (id<1> ID) {…});

});

q.submit([&] (handler& h) {h.parallel_for(R, [=] (id<1> ID) {…});

});

q.submit([&] (handler& h) {h.parallel_for(R, [=] (id<1> ID) {…});

});

Page 37: データ並列 C++ (インテル® oneAPI DPC++) の紹介

データ・フロー・パイプ

制御サイドバンドを持つデータ

• 転送と同期の細粒度の情報

• 空間アーキテクチャーでは重要 (FPGA)

カーネルB

カーネルA

カーネルA

カーネルA

カーネルA

ホストプログラム

I/O インターフェイス(ネットワークなど)

カーネル1

カーネル3

カーネル2

カーネル4

FIFO

FIFO

FIFO

Page 38: データ並列 C++ (インテル® oneAPI DPC++) の紹介

オプションのカーネルラムダ命名

SYCL* 1.2.1 標準では、すべてのカーネルで一意の名前付けが必要です

• ファンクター・クラス・タイプ

• ラムダ式のテンプレート・タイプ名

DPC++ はラムダ式のこの要件を緩和します

• ホストコードとデバイスコードの両方でDPC++ コンパイラーを使用する必要があります

• コンパイラー・スイッチを指定します

• -fsycl-unnamed-lambda

q.submit([&] (handler& h) {auto R = range<1>{N};

h.parallel_for(R, [=](id<1> ID) {

auto i = ID[0];C[i] = A[i] + B[i];

});});

Page 39: データ並列 C++ (インテル® oneAPI DPC++) の紹介

DPC++ は、CPU とアクセラレーターの両方で妥協のない生産性とパフォーマンスをもたらす、標準化ベースのクロスアーキテクチャー言語です

• SYCL* 1.2.1 標準に新機能を拡張しています

タスクグラフを使用したカーネルベースのモデル

新機能はコミュニティーのプロジェクトによって開発されています

• https://github.com/intel/llvm (英語)

• 問題のレポートや提案を歓迎します!!

まとめ

Page 40: データ並列 C++ (インテル® oneAPI DPC++) の紹介

XPU によってアクセラレートされる未来に備えて、すぐにインテル® oneAPI から始めましょう

oneAPI 仕様 はoneAPI.com (英語) から入手できます

oneAPI 向けのインテル® DevCloud

(software.intel.com/devcloud/oneAPI (英語)) でインテル® oneAPI を使用して、さまざまなインテルの

データセントリックなアーキテクチャーでコードとワークロードをテストできます

関連情報とダウンロードインテル® ツールキットは、

software.intel.com/oneapi (英語)からダウンロードできます

Page 41: データ並列 C++ (インテル® oneAPI DPC++) の紹介

ワークロードの評価

ヘテロジニアス・アプリケーションのビルド

インテル® oneAPI ツールキットを学習

データ並列 C++ を学習

プロジェクトのプロトタイプを作成

ダウンロード不要 | ハードウェア不要 | インストール不要 | セットアップと設定不要

わずかな時間で起動および実行できます!!

インテル® oneAPI ツールキットを使用して、さまざまなインテルの CPU、GPU、FPGA でワークロードを開発、テスト、および実行する開発向けのサンドボックス

インテル® oneAPI を使用する

インテル® DevCloud

software.intel.com/en-us/devcloud/oneapi (英語)

Page 42: データ並列 C++ (インテル® oneAPI DPC++) の紹介

最適化に関する注意事項

インテル® コンパイラーでは、インテル® マイクロプロセッサーに限定されない最適化に関して、他社製マイクロプロセッサー用に同等の最適化を行えないことがあります。これには、インテル® ストリーミング SIMD 拡張命令 2、インテル® ストリーミング SIMD 拡張命令 3、インテル® ストリーミング SIMD 拡張命令 3 補足命令などの最適化が該当します。インテルは、他社製マイクロプロセッサーに関して、いかなる最適化の利用、機能、または効果も保証いたしません。本製品のマイクロプロセッサー依存の最適化は、インテル® マイクロプロセッサーでの使用を前提としています。インテル® マイクロアーキテクチャーに限定されない最適化のなかにも、インテル® マイクロプロセッサー用のものがあります。この注意事項で言及した命令セットの詳細については、該当する製品のユーザー・リファレンス・ガイドを参照してください。注意事項の改訂 #20110804

法務上の注意書きと最適化に関する注意事項

本資料には、開発中の製品、サービスおよびプロセスについての情報が含まれています。本資料に含まれる情報は予告なく変更されることがあります。

インテルのテクノロジーを使用するには、対応したハードウェア、ソフトウェア、またはサービスの有効化が必要となる場合があります。詳細については、OEM または販売店にお問い合わせいただくか、https://www.intel.co.jp/ を参照してください。

性能に関するテストに使用されるソフトウェアとワークロードは、性能がインテル® マイクロプロセッサー用に最適化されていることがあります。SYSmark* や MobileMark* などの性能テストは、特定のコンピューター・システム、コンポーネント、ソフトウェア、操作、機能に基づいて行ったものです。結果はこれらの要因によって異なります。製品の購入を検討される場合は、他の製品と組み合わせた場合の本製品の性能など、ほかの情報や性能テストも参考にして、パフォーマンスを総合的に評価することをお勧めします。性能やベンチマーク結果について、さらに詳しい情報をお知りになりたい場合は、https://www.intel.com/benchmarks (英語) を参照してください。

本資料は、(明示されているか否かにかかわらず、また禁反言によるとよらずにかかわらず) いかなる知的財産権のライセンスも許諾するものではありません。インテルは、明示されているか否かにかかわらず、いかなる保証もいたしません。ここにいう保証には、商品適格性、特定目的への適合性、および非侵害性の黙示の保証、ならびに履行の過程、取引の過程、または取引での使用から生じるあらゆる保証を含みますが、これらに限定されるわけではありません。

© 2021 Intel Corporation. 無断での引用、転載を禁じます。Intel、インテル、Intel ロゴは、アメリカ合衆国および / またはその他の国における Intel Corporation またはその子会社の商標です。Khronos* は登録商法であり、SYCL* は Khronos Group, Inc の商標です。

* その他の社名、製品名などは、一般に各社の表示、商標または登録商標です。

Page 43: データ並列 C++ (インテル® oneAPI DPC++) の紹介

質疑応答

Page 44: データ並列 C++ (インテル® oneAPI DPC++) の紹介