SYCL* を使用した C++ のデータ並列処理

C++ における生産性の高いデータ並列プログラミング向けのオープンで、複数ベンダーによる、マルチ・アーキテクチャーのサポートは、SYCL* をサポートする標準 C++ によって実現されます。SYCL* (‘シクル’ と読みます) は、単一ソースファイルに含まれるアプリケーションのホストコードとカーネルコードと標準の ISO C++ を使用して、異種プロセッサーのコードを記述できるようにする、ロイヤリティー・フリーのクロスプラット・フォームの抽象化レイヤーです。DPC++ オープンソース・プロジェクトは、LLVM C++ コンパイラーに SYCL* のサポートを追加しています。

キューとラムダを使用した簡単なサンプルコード

SYCL* の導入を示す最良の方法は、簡単なサンプルを使用することです。SYCL* は最新の C++ をベースとしているため、この例ではラムダ式や一様初期化など近年 C++ に追加されたいくつかの機能を使用しています。開発者がこれらの機能に精通していなくても、それらの意味と機能はサンプルから明らかになります。SYCL* によるプログラミングの経験を積んでいくと、これらの新しい C++ の機能は自然に受け入れられるでしょう。

次のサンプルコードは、a[0] = 0, a[1] = 1、... のように配列の各要素をそのインデックス値に設定します。

#include <CL/sycl.hpp> 
#include <iostream> 

constexpr int num=16; 
using namespace sycl; 

int main() { 
  auto r = range{num}; 
  buffer<int> a{r}; 

  queue{}.submit([&](handler& h) { 
    accessor out{a, h}; 
    h.parallel_for(r, [=](item<1> idx) { 
      out[idx] = idx; 
    }); 
  }); 

  host_accessor result{a}; 
  for (int i=0; i<num; ++i) 
    std::cout << result[i] << "\n"; 
}

最初に気づくことは、ソースファイルが 1 つしかないことです。つまり、ホストコードとオフロードされるアクセラレーター・コードの両方がこの単一のソースファイルから生成されます。次に注目すべき点は、構文が標準の C++ であるということです。並列処理を表現する新しいキーワードやプラグマは使用されていません。代わりに、並列処理は C++ クラスを介して表現されています。例えば、9 行目にある buffer クラスはデバイスにオフロードされるデータを表し、11 行目の queue クラスはホストからアクセラレーターへの接続を表します。

ロジックは次のように動作します。8 行目と 9 行目で、初期値を持たない 16 個のint 要素のバッファーを作成します。このバッファーは配列のように作用します。11 行目でアクセラレーター・デバイスに接続する queue を作成します。この簡単な例では、SYCL* ランタイムがデフォルトのアクセラレーター・デバイスを選択しますが、アプリケーションによっては、システムのトポロジーを調査して特定のアクセラレーションを選択することもできます。キューが作成されると、この例では submit() メンバー関数を呼び出して、アクセラレーターにワークを送信します。この submit() 関数の引数はラムダ関数であり、ホスト上ですぐに実行されます。ラムダ関数は次の 2 つのことを行います。1 つは、12 行目でアクセサーout を作成します。アクセサーはバッファーの要素を書き込むことができます。次に、13 行目で parallel_for() 関数を呼び出してコードをアクセラレーターで実行します。

parallel_for() の呼び出しには 2 つの引数があります。1 つはラムダ関数であり、もう 1 つはバッファー内の要素数を示すレンジ・オブジェクト “r” です。SYCL* は、ラムダ関数がレンジ内のインデックスごとに一度 (バッファー要素ごとに 1 回)、アクセラレーターで呼び出されるように調整します。ラムダは、12 行目で作成された out アクセサーを使用してバッファー要素に値を割りてるだけです。この簡単な例では、ラムダ呼び出し間に依存関係がないため、SYCL* はアクセラレーターで最も効率良い方法で自由に並行して実行できます。

parallel_for() を呼び出した後、ホストのコードはアクセラレーターの完了を待たずに処理を続行します。ホストが次に行うことは、18 行目でバッファーの要素を読み取る host_accessor を作成することです。SYCL* は、このバッファーがアクセラレーターによって書き込まれたことを認識するため、host_accessor コンストラクター (18 行目) は、parallel_for() によって送信されたワークが完了するまでブロックされます。アクセラレーターのワークが完了すると、ホストコードは 18 行目以降を続行し、out アクセサーを使用してバッファーから値を読み取ります。

関連情報

この SYCL* の概要は、完全なチュートリアルを目指すものではなく、言語機能の一部分を紹介するだけです。ローカルメモリー、バリアー、SIMD など一般にアクセラレーター・ハードウェアで使用するため学ぶべきことはほかにもたくさんあります。一度に複数のアクセラレーター・デバイスにワークを送信する機能もあり、1 つのアプリケーションが複数のデバイスで同時にワークを並行して実行することもできます。

以下のリソースは、SYCL* を学習するのに役立ちます: