C++ AMP の概要
C++ で加速された大きい並列 AMP (C++) は、別々のグラフィックス カードの単位を (GPU) するグラフィックス処理などのデータ並列ハードウェアを使用して、C++ コードの実行を高速化します。C++ AMP を使用して実装が異なるハードウェアで加速並列で使用できるように、多次元データのアルゴリズムをコーディングすることができます。C++ AMP のプログラミング モデルは、多次元配列のインデックス付け、メモリのコピー、タイル、数学関数のライブラリが含まれています。パフォーマンスを向上できるように、データは CPU GPU から戻るとにそれぞれのかを制御するには、C++ AMP の言語拡張機能を使用できます。
システム要件
Windows 7、Windows 8、Windows Server 2008 R2、または Windows Server 2012
DirectX 11 の機能レベル 11.0 以降のハードウェア
ソフトウェアのエミュレーターでデバッグでは、Windows 8 か Windows Server 2012 が必要です。ハードウェアでのデバッグするには、のグラフィックス カードのドライバーをインストールする必要があります。詳細については、「GPU コードのデバッグ」を参照してください。
Introduction
次の 2 種類の例では C++ AMP.の主要コンポーネントについて説明します。2 個の 1 次元配列の対応する要素を追加するとします。たとえば、{7, 9, 11, 13, 15}を取得するには {1, 2, 3, 4, 5} と {6, 7, 8, 9, 10} を追加する必要があります。C++ AMP を使用しない場合、数を追加し、結果を表示する次のコードを記述する場合があります。
#include <iostream>
void StandardMethod() {
int aCPP[] = {1, 2, 3, 4, 5};
int bCPP[] = {6, 7, 8, 9, 10};
int sumCPP[5];
for (int idx = 0; idx < 5; idx++)
{
sumCPP[idx] = aCPP[idx] + bCPP[idx];
}
for (int idx = 0; idx < 5; idx++)
{
std::cout << sumCPP[idx] << "\n";
}
}
コードの重要な部分は次のとおりです。:
data: データは、3 台の配列で構成されています。すべて同じランク (1) と (5) 長さがあります。
:イテレーション for の最初のループは配列の要素を繰り返すことによって機構を提供します。合計を計算するために実行するコードは for の最初のブロックに含まれています。
インデックス: idx の変数は、配列の各要素にアクセスします。
C++ AMP を使用して、次のコードのように記述することがあります。
#include <amp.h>
#include <iostream>
using namespace concurrency;
const int size = 5;
void CppAmpMethod() {
int aCPP[] = {1, 2, 3, 4, 5};
int bCPP[] = {6, 7, 8, 9, 10};
int sumCPP[size];
// Create C++ AMP objects.
array_view<const int, 1> a(size, aCPP);
array_view<const int, 1> b(size, bCPP);
array_view<int, 1> sum(size, sumCPP);
sum.discard_data();
parallel_for_each(
// Define the compute domain, which is the set of threads that are created.
sum.extent,
// Define the code to run on each thread on the accelerator.
[=](index<1> idx) restrict(amp)
{
sum[idx] = a[idx] + b[idx];
}
);
// Print the results. The expected output is "7, 9, 11, 13, 15".
for (int i = 0; i < size; i++) {
std::cout << sum[i] << "\n";
}
}
同じ基本要素ですが、C++ AMP 構成体が使用されます:
data: 3 個の AMP C++ array_view のオブジェクトの構築に C++ の配列を使用します。array_view のオブジェクトを構築するには 4 の値を指定します: 各ディメンションの array_view のオブジェクトのデータ値要素の型、ランク、および長さ。型パラメーターとして順位と型が渡されます。データと長さは、コンストラクターのパラメーターとして渡されます。この例では、コンストラクターに渡される C++ 配列は、1 次元です。順位がと長さは array_view のオブジェクト データの四角形の構築に使用された配列にデータを設定値が使用されます。array_view のクラスに似ており、このトピックで後述するインターフェイスを持つランタイム ライブラリには、array クラスが含まれています。
:イテレーション parallel_for_each 関数 (C++ AMP) は、データ要素を通じて繰り返す機構、すなわち計算のドメインを提供します。この例では、計算のドメインは、sum.extentによって指定されます。実行するコードは、ラムダ式、または カーネル関数に含まれています。restrict(amp) は C++ AMP 加速ができる C++ 言語のサブセットのみ使用されることを示します。
インデックス: index クラス の変数、idxは、1 種類のランクと、array_view のオブジェクトのランクと一致するように宣言されます。インデックスを使用して、array_view のオブジェクトの個々の要素にアクセスできます。
データ形式を、指定したファイルです: インデックスと範囲
カーネル コードを実行する前にデータ値を定義し、データの形式を宣言する必要があります。すべてのデータは、配列 (四角形) として定義されランク (次元数) を持つように配列を定義できます。データはディメンションのいずれかの任意のサイズです。
インデックスのクラス
index クラス は array または array_view のオブジェクトに 1 個のオブジェクトへの各ディメンションの原点からのオフセットをカプセル化して位置を指定します。配列の位置にアクセスする場合、整数のインデックスのリストの代わりにの添字演算子への []、index のオブジェクトを渡します。array::operator() 演算子 か array_view::operator() 演算子を使用して各ディメンションの要素にアクセスできます。
次の例では array_view の 1 次元オブジェクトに 3 番目の要素を指定する 1 次元インデックスを作成します。インデックスが array_view のオブジェクトの 3 番目の要素を印刷するために使用されます。出力は 3.です。
int aCPP[] = {1, 2, 3, 4, 5};
array_view<int, 1> a(5, aCPP);
index<1> idx(2);
std::cout << a[idx] << "\n";
// Output: 3
次の例では array_view の次元のオブジェクトの要素を = 1 行と列 = 指定する 2 次元のインデックスを作成します。index のコンストラクターの最初のパラメーターは行のコンポーネントで、2 番目のパラメーターは、列のコンポーネントです。出力は 6.です。
int aCPP[] = {1, 2, 3,
4, 5, 6};
array_view<int, 2> a(2, 3, aCPP);
index<2> idx(1, 2);
std::cout << a[idx] << "\n";
// Output: 6
次の例では array_view 次元のオブジェクトの要素を深さ = 0 = 1、行、および列 = 3 次元指定するインデックスを作成します。2 番目のパラメーターが行のコンポーネントで、3 番目のパラメーターが列のコンポーネントであることに最初のパラメーターである構造コンポーネント注意してください。出力は 8.です。
int aCPP[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
array_view<int, 3> a(2, 3, 4, aCPP);
// Specifies the element at 3, 1, 0.
index<3> idx(0, 1, 3);
std::cout << a[idx] << "\n";
// Output: 8
extent クラス
extent クラス (C++ AMP) は array または array_view のオブジェクトの各ディメンションでデータの長さを指定します。範囲を作成し、array または array_view のオブジェクトを作成できます。また array_view の既存の array またはオブジェクトのエクステントを取得できます。次の例では array_view のオブジェクトの各次元の範囲の長さを出力します。
int aCPP[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// There are 3 rows and 4 columns, and the depth is two.
array_view<int, 3> a(2, 3, 4, aCPP);
std::cout << "The number of columns is " << a.extent[2] << "\n";
std::cout << "The number of rows is " << a.extent[1] << "\n";
std::cout << "The depth is " << a.extent[0]<< "\n";
std::cout << "Length in most significant dimension is " << a.extent[0] << "\n";
次の例では array_view のコンストラクターで、明示的なパラメーターを使用する代わりに、前の例でオブジェクトと同じ配列を表す、この例は extent のオブジェクトを作成します array_view のオブジェクト。
int aCPP[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
extent<3> e(2, 3, 4);
array_view<int, 3> a(e, aCPP);
std::cout << "The number of columns is " << a.extent[2] << "\n";
std::cout << "The number of rows is " << a.extent[1] << "\n";
std::cout << "The depth is " << a.extent[0] << "\n";
アクセラレータに移動します:データ 配列と array_view
アクセラレータにデータを実行する 2 種類のデータ コンテナーがランタイム ライブラリで定義されます。これらは、array クラス と array_view クラスです。array のクラスは、オブジェクトの構築時にデータの深いコピーを作成するコンテナー クラスです。array_view のクラスは、カーネル関数がデータにアクセスするデータをコピー ラッパー クラスです。データ ソースのデバイスが、必要に応じてデータはコピーされません。
配列クラス
array のオブジェクトを構築すると、データの詳細コピーでは、アクセラレータでデータ セットへのポインターを含むコンストラクターを使用して作成されます。カーネル関数は、アクセラレータのコピーを変更します。カーネル関数の実行が終了すると、ソースのデータ構造にデータをコピーし直す必要があります。次の例では、10.でベクターの各要素をインクリメントします。カーネル関数が終了したらベクターのオブジェクトにこのデータをコピーするには、ベクターの変換演算子 が使用されます。
std::vector<int> data(5);
for (int count = 0; count < 5; count++)
{
data[count] = count;
}
array<int, 1> a(5, data.begin(), data.end());
parallel_for_each(
a.extent,
[=, &a](index<1> idx) restrict(amp)
{
a[idx] = a[idx] * 10;
}
);
data = a;
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n";
}
array_view のクラス
array_view に array のクラスと同じメンバーがほとんどですが、基になる動作は同じではありません。array_view のコンストラクターに渡されるデータは、GPU で array のコンストラクターであると同時に複製されません。代わりに、データはアクセラレータとカーネル関数が実行されたときにコピーします。したがって、同じデータを使用する array_view の 2 種類のオブジェクトを作成する場合は、array_view のオブジェクトが同じメモリ空間を示します。この場合、マルチスレッド アクセスを同期する必要があります。array_view のクラスを使用する主な利点は、必要な場合のみ、データを実行することです。
配列と array_view の比較
次の表は array_view の array とクラスの類似点と相違点の概要を示します。
説明 |
配列クラス |
array_view のクラス |
---|---|---|
順位が決定されたとき |
コンパイル時。 |
コンパイル時。 |
範囲が決定されたとき |
実行時。 |
実行時。 |
Shape |
四角形。 |
四角形。 |
データ ストレージ |
データのコンテナーです。 |
データのラッパーです。 |
コピー |
定義の明示的な詳細コピー。 |
これがカーネル関数を使用してアクセスされた場合の暗黙のコピー。 |
データの取得 |
CPU のオブジェクトに返される配列のデータをコピーして実行します。 |
array_view の直接アクセスして array_view::synchronize メソッド のデータを元のコンテナーにアクセスするつもりでまたはを呼び出すことによってオブジェクトします。 |
データ上のコードの実行: parallel_for_each
parallel_for_each 関数は、array または array_view のオブジェクトのデータに対してアクセラレータで実行するコードを定義します。このトピックの概要の次のコードを検討してください。
#include <amp.h>
#include <iostream>
using namespace concurrency;
void AddArrays() {
int aCPP[] = {1, 2, 3, 4, 5};
int bCPP[] = {6, 7, 8, 9, 10};
int sumCPP[5] = {0, 0, 0, 0, 0};
array_view<int, 1> a(5, aCPP);
array_view<int, 1> b(5, bCPP);
array_view<int, 1> sum(5, sumCPP);
parallel_for_each(
sum.extent,
[=](index<1> idx) restrict(amp)
{
sum[idx] = a[idx] + b[idx];
}
);
for (int i = 0; i < 5; i++) {
std::cout << sum[i] << "\n";
}
}
parallel_for_each のメソッドは 2 個の引数、計算のドメインとラムダ式を設定します。
計算のドメインは、extent のオブジェクトまたは並列実行用に作成するスレッドのセットを定義する tiled_extent のオブジェクトです。1 種類のスレッドで計算のドメインの各要素に対して生成されます。この場合、extent のオブジェクトは、1 次元で、5 個の要素があります。したがって、5 種類のスレッドが呼び出されます。
ラムダ式は、 各スレッドで実行するコードを定義します。capture 句は、[=]できますが、ラムダ式の本体は、aこの場合、b指定し、sumすべてのキャプチャ変数にアクセスします。この例では、パラメーター リストは idxという名前の index の 1 次元変数を作成します。idx[0] の値は、最初のスレッドの 0、各スレッドの 1 だけ増加します。restrict(amp) は C++ AMP 加速ができる C++ 言語のサブセットのみ使用されることを示します。制限の修飾子を持つ関数の制限は 制限の句 (C++ AMP)で説明します。詳細については、「ラムダ式の構文」を参照してください。
ラムダ式は、実行するコードを含めることができるか、または別のカーネル関数を呼び出すことができます。カーネル関数は restrict(amp) 修飾子を含める必要があります。次の例は前の例と同じですが、別のカーネル関数を呼び出します。
#include <amp.h>
#include <iostream>
using namespace concurrency;
void AddElements(index<1> idx, array_view<int, 1> sum, array_view<int, 1> a, array_view<int, 1> b) restrict(amp)
{
sum[idx] = a[idx] + b[idx];
}
void AddArraysWithFunction() {
int aCPP[] = {1, 2, 3, 4, 5};
int bCPP[] = {6, 7, 8, 9, 10};
int sumCPP[5] = {0, 0, 0, 0, 0};
array_view<int, 1> a(5, aCPP);
array_view<int, 1> b(5, bCPP);
array_view<int, 1> sum(5, sumCPP);
parallel_for_each(
sum.extent,
[=](index<1> idx) restrict(amp)
{
AddElements(idx, sum, a, b);
}
);
for (int i = 0; i < 5; i++) {
std::cout << sum[i] << "\n";
}
}
加速コード: タイル、およびバリア
タイルを使用して追加のアクセラレータを取得できます。並べて表示する場合は、四角形のサブセットまたはタイルにスレッドを分割します。コーディングすることなく、データのセットに基づく適切なタイトルのサイズおよびアルゴリズムが決まります。スレッドごとに、全体 array か array_view に関連するデータ要素の グローバル 位置とタイルに関連して ローカルな 位置へのアクセスにアクセスできます。ローカルのインデックス値を使用してグローバルから場所に移動のインデックス値にコードを記述する必要がないため、コードを簡略化します。タイルを使用し、計算のドメインの extent::tile メソッド を parallel_for_each のメソッドでを呼び出し、tiled_index のオブジェクトを、ラムダ式で使用します。
一般的なアプリケーションでは、タイルの要素はなんらかの方法で関連付けられ、コードはタイルを渡す値にアクセスして追跡する必要があります。これを実現するために tile_static キーワード のキーワードと tile_barrier::wait メソッド を使用します。tile_static のキーワードを持つ変数に各タイルに全体のタイルを渡すと、範囲変数のインスタンスが作成されます。変数へのタイル スレッドのアクセスの同期を処理する必要があります。tile_barrier::wait メソッド は、タイル内のすべてのスレッドが tile_barrier::waitの呼び出しに到達するまで現在のスレッドの実行を停止します。したがって tile_static の変数を使用してタイルを渡す値を収集できます。次に、すべての値にアクセスする計算を終了できます。
次の図は、タイルに配置されたサンプリング データの次元配列を表します。
次のコード例は、前の図からサンプリング データを使用します。コードは、タイルの値の平均とタイルの各値を置き換えます。
// Sample data:
int sampledata[] = {
2, 2, 9, 7, 1, 4,
4, 4, 8, 8, 3, 4,
1, 5, 1, 2, 5, 2,
6, 8, 3, 2, 7, 2};
// The tiles:
// 2 2 9 7 1 4
// 4 4 8 8 3 4
//
// 1 5 1 2 5 2
// 6 8 3 2 7 2
// Averages:
int averagedata[] = {
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
};
array_view<int, 2> sample(4, 6, sampledata);
array_view<int, 2> average(4, 6, averagedata);
parallel_for_each(
// Create threads for sample.extent and divide the extent into 2 x 2 tiles.
sample.extent.tile<2,2>(),
[=](tiled_index<2,2> idx) restrict(amp)
{
// Create a 2 x 2 array to hold the values in this tile.
tile_static int nums[2][2];
// Copy the values for the tile into the 2 x 2 array.
nums[idx.local[1]][idx.local[0]] = sample[idx.global];
// When all the threads have executed and the 2 x 2 array is complete, find the average.
idx.barrier.wait();
int sum = nums[0][0] + nums[0][1] + nums[1][0] + nums[1][1];
// Copy the average into the array_view.
average[idx.global] = sum / 4;
}
);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 6; j++) {
std::cout << average(i,j) << " ";
}
std::cout << "\n";
}
// Output:
// 3 3 8 8 3 3
// 3 3 8 8 3 3
// 5 5 2 2 4 4
// 5 5 2 2 4 4
数値演算ライブラリ
C++ AMP は 2 個の数値演算ライブラリが含まれます。Concurrency::precise_math 名前空間 の倍精度ライブラリは倍精度関数をサポートします。また、単精度関数にハードウェアの倍精度サポートが必要ですが、サポートを提供します。これは、に C99 指定 (ISO/IEC 9899)準拠します。アクセラレータが倍精度をサポートする必要があります。これは accelerator::supports_double_precision データ メンバーに値のチェックによってするかどうかを確認できます。高速な数値演算ライブラリは、Concurrency::fast_math 名前空間で、数値演算関数の別のセットが含まれます。float のオペランドだけをサポートするこれらの関数はすばやく実行しますが、倍精度の数値演算ライブラリの、精度ではありません。関数は <amp_math.h> のヘッダー ファイルに含まれる、すべてが restrict(amp)で宣言されています。<cmath> のヘッダー ファイルの関数は fast_math と precise_math の名前空間の両方にインポートします。restrict のキーワードが <cmath> のバージョンおよび C++ AMP のバージョンを区別するために使用されます。次のコードは、計算のドメインにある各値の高速なメソッドを使用して、Base 10 の対数を計算します。
#include <amp.h>
#include <amp_math.h>
#include <iostream>
using namespace concurrency;
void MathExample() {
double numbers[] = { 1.0, 10.0, 60.0, 100.0, 600.0, 1000.0 };
array_view<double, 1> logs(6, numbers);
parallel_for_each(
logs.extent,
[=] (index<1> idx) restrict(amp) {
logs[idx] = concurrency::fast_math::log10(logs[idx]);
}
);
for (int i = 0; i < 6; i++) {
std::cout << logs[i] << "\n";
}
}
グラフィックス ライブラリ
C++ AMP は、使用するグラフィックスのプログラミング用にデザインされたグラフィックス ライブラリが含まれます。このライブラリは、ネイティブなグラフィックス機能をサポートするデバイスでのみ使用されます。メソッドは Concurrency::graphics 名前空間 にあり、<amp_graphics.h> のヘッダー ファイルに入っています。グラフィックス ライブラリの主要部分は次のとおりです:
texture クラス: メモリまたはファイルからのテクスチャを作成するために、テクスチャのクラスを使用できます。テクスチャ、データを含む、割り当てとコピー コンストラクターに関する標準テンプレート ライブラリ (STL) のコンテナーに似ています。配列に似ており。詳細については、「STL コンテナー」を参照してください。texture のクラスのテンプレート パラメーターは、要素の型、ランクです。ランクは 1、2、または 3.のいずれかになります。要素型は、このトピックの後半で説明する短いベクターの 1 種類のいずれかです。
writeonly_texture_view クラス: すべてのテクスチャへの書き込み専用アクセスを提供します。
短いベクターのライブラリ) : 一連の int、uint、float、double、標準、または unormに基づく長さ 2、3、および 4 の短いベクターの型を定義します。
Windows ストア Apps
他の C++ ライブラリのように、Windows ストア の apps で C++ AMP を使用できます。これらの文書は、C、C++、Visual Basic、JavaScript を使用して作成された apps に AMP C++ コードを含める方法を記述します:
チュートリアル: C++ および JavaScript からこれを呼び出すことで Windows の基本的なランタイム コンポーネントを作成します
Bing は、JavaScript の概要のオプティマイザー、ウィンドウの格納および C++ アプリケーションをマップします
C++ AMP と同時実行ビジュアライザー
同時実行ビジュアライザーは C++ AMP コードのパフォーマンスを分析するためのサポートが含まれます。これらの文書は、これらの機能です:
パフォーマンスに関する推奨事項
符号なし整数の剰余と N/A に符号付き整数の剰余となしよりも優れたパフォーマンスがあります。これは、可能であれば、符号なし整数を使用することをお勧めします。