C/C++ OpenMP* と SYCL* のコンポービリティー

oneAPI プログラミング・モデルは、OpenMP* オフロードをサポートする LLVM/Clang ベースの統合コンパイラーを提供します。これにより、OpenMP* 構造を使用してホスト側のアプリケーションを並列化するか、ターゲットデバイスにオフロードするシームレスな統合が可能になります。インテル® oneAPI ベース・ツールキットで利用可能なインテル® oneAPI DPC++/C++ コンパイラーは、制限付きで OpenMP* および SYCL* のコンポーザビリティーをサポートします。単一のアプリケーションでは、OpenMP* target 領域または SYCL* 構造を使用して、さまざまな関数やコードセグメントなどのコード領域の実行をデバイスにオフロードできます。

OpenMP* と SYCL* オフロード構造は、別々のファイル、同じファイル、または同じ関数 (制限付き) で使用できます。OpenMP* および SYCL* オフロードコードは、実行可能ファイル、静的ライブラリー、またはさまざまな組み合わせで一緒にバンドルできます。

SYCL* は CPU でデバイスコードを実行する場合、インテル® TBB のランタイムを使用します。そのため、CPU で OpenMP* と SYCL* の両方を使用するとスレッドのオーバーサブスクリプションが発生する可能性があります。システムで実行されるワークロードのパフォーマンスを解析することで、この問題が生じているか確認することができます。

制限事項

同じアプリケーションで OpenMP* と SYCL* 構造を混在する場合、いくつかの考慮すべき制限があります。

  • OpenMP* ディレクティブは、デバイスで実行される SYCL* カーネル内では使用できません。同様に、SYCL* コードは、OpenMP* target 領域内では使用できません。ただし、ホスト CPU で実行される OpenMP* コード内で SYCL* 構造を使用することはできます。

  • プログラム内の OpenMP* および SYCL* デバイス領域は相互依存関係を持つことができません。例えば、デバイスコードの SYCL* 領域で定義された関数は、同じデバイスで実行される OpenMP* コードから呼び出すことはできません (その逆も同様)。OpenMP* デバイス領域と SYCL* デバイス領域は個別にリンクされ、個別のバイナリーとしてコンパイラーによって生成されるファットバイナリーを構成します。

  • 現時点では、OpenMP* と SYCL* ランタイム・ライブラリー間での直接的な相互作用はサポートされていません。例えば、OpenMP* API で生成されたデバイス・メモリー・オブジェクトは、SYCL* コードからはアクセスできません。OpenMP* で生成されたデバイス・メモリー・オブジェクトを SYCL* コードで使用すると、未定義の動作となります。

次のコードは、SYCL* と OpenMP* オフロード構造を同じアプリケーションで使用する例を示します。

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

float computePi(unsigned N) { 
  float Pi; 
#pragma omp target map(from : Pi) 
#pragma omp parallel for reduction(+ : Pi) 
  for (unsigned I = 0; I < N; ++I) { 
    float T = (I + 0.5f) / N; 
    Pi += 4.0f / (1.0 + T * T); 
  } 
  return Pi / N; 
} 

void iota(float *A, unsigned N) { 
  cl::sycl::range<1> R(N); 
  cl::sycl::buffer<float, 1> AB(A, R); 
  cl::sycl::queue().submit([&](cl::sycl::handler &cgh) {
    auto AA = AB.template get_access<cl::sycl::access::mode::write>(cgh); 
    cgh.parallel_for<class Iota>(R, [=](cl::sycl::id<1> I) { 
      AA[I] = I; 
    }); 
  }); 
} 

int main() { 
  std::array<float, 1024u> Vec; 
  float Pi; 

#pragma omp parallel sections 
  { 
#pragma omp section 
    iota(Vec.data(), Vec.size()); 
#pragma omp section 
    Pi = computePi(8192u); 
  } 

  std::cout << "Vec[512] = " << Vec[512] << std::endl;
  std::cout << "Pi = " << Pi << std::endl; 
  return 0; 
}

サンプルコードをコンパイルするには、次のコマンドを使用します: icpx -fsycl -fiopenmp -fopenmp-targets=spir64 offloadOmp_dpcpp.cpp

ここで、

  • -fsycl オプションは SYCL* を有効にします

  • -fiopenmp -fopenmp-targets=spir64 オプションは OpenMP* を有効にします

次はサンプルコードからの出力例となります。

./a.out 
Vec[512] = 512 
Pi = 3.14159

コードに OpenMP* オフロードが含まれておらず、通常の OpenMP* コードのみが含まれる場合、 -fopenmp-targets を省いた次のコマンドを使用します: icpx -fsycl -fiopenmp omp_dpcpp.cpp