タイルの使用
タイリングを使用すると、アクセラレータのアプリケーションを最大化できます。タイルと同じ矩形のサブセットにスレッドを分割またはタイル。適切なタイルのサイズし、並べて表示されたアルゴリズムを使用する場合は、AMP の C++ コードからさらに多くの高速化を取得できます。タイリングの基本的なコンポーネントは次のとおりです。
tile_static変数。タイリングの主な利点は、パフォーマンスの向上からですtile_staticへのアクセス。データにアクセスするtile_staticメモリは、グローバル空間のデータへのアクセスよりも大幅に高速 (arrayまたはarray_viewオブジェクト)。インスタンスは、 tile_static変数が各タイルを作成し、タイルのすべてのスレッドは、変数へのアクセスがあります。代表的なタイルのアルゴリズムでは、データにコピー tile_staticグローバル ・ メモリから 1 回のメモリと、何回ものアクセス、 tile_staticメモリ。
tile_barrier::wait メソッド.呼び出しをtile_barrier::waitすべてのスレッドで同じタイルへの呼び出しに到達するまで、現在のスレッドの実行が中断されますtile_barrier::wait。スレッドが実行される、のみ、タイルのスレッドへの呼び出しは、過去の実行は中断されます順序を保証することはできませんtile_barrier::waitすべてのスレッドの呼び出しに達するまで。これを使用してつまり、 tile_barrier::waitメソッドは、スレッドのスレッドによってごとではなく、タイル-タイルによってごとにタスクを実行できます。代表的なタイルのアルゴリズムを初期化するコードが、 tile_staticの呼び出しに続くメモリ全体のタイルのtile_barrer::wait。次のコードtile_barrier::wait 、すべてへのアクセスを必要とする演算が含まれています、 tile_static値。
ローカルおよびグローバル インデックスを作成します。スレッド全体を基準とするインデックスへのアクセスがあるarray_viewまたはarrayオブジェクトやタイルを基準とするインデックス。ローカル インデックスを使用して、コードの読み取り、およびデバッグするには、わかりやすくなります。一般に、ローカルのインデックスへのアクセス使用tile_static変数、およびグローバル アクセスするインデックスarrayとarray_view変数。
tiled_extent クラス および tiled_index クラス。使用して、 tiled_extentのではなくオブジェクト、 extentオブジェクト、 parallel_for_eachを呼び出します。使用して、 tiled_indexのではなくオブジェクト、 indexオブジェクト、 parallel_for_eachを呼び出します。
タイリングを利用するには、アルゴリズムする必要があります計算ドメインをタイルに分割し、タイルにデータをコピー tile_staticの高速アクセス用の変数。
グローバルの例、タイル、およびローカル インデックス
次の図は 8 × 9 マトリックスの 2 x 3 のタイルに配置されているデータを表します。
次の例は、グローバル、タイル表示し、ローカル インデックスこのマトリックスを並べて表示します。array_viewオブジェクトの型の要素を使用して作成Description。Descriptionを保持するグローバルなタイル、およびローカルのインデックスは、マトリックス内の要素のします。コードへの呼び出しをparallel_for_each 、グローバル値、タイル、およびローカル インデックスの各要素を設定します。出力の値を表示する、 Descriptionの構造体。
#include <iostream>
#include <iomanip>
#include <Windows.h>
#include <amp.h>
using namespace concurrency;
const int ROWS = 8;
cons tint COLS = 9;
// tileRow and tileColumn specify the tile that each thread is in.
// globalRow and globalColumn specify the location of the thread in the array_view.
// localRow and localColumn specify the location of the thread relative to the tile.
struct Description {
int value;
int tileRow;
int tileColumn;
int globalRow;
int globalColumn;
int localRow;
int localColumn;
};
// A helper function for formatting the output.
void SetConsoleColor(int color) {
int colorValue = (color == 0) ? 4 : 2;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colorValue);
}
// A helper function for formatting the output.
void SetConsoleSize(int height, int width) {
COORD coord; coord.X = width; coord.Y = height;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coord);
SMALL_RECT* rect = new SMALL_RECT();
rect->Left = 0; rect->Top = 0; rect->Right = width; rect->Bottom = height;
SetConsoleWindowInfo(GetStdHandle(STD_OUTPUT_HANDLE), true, rect);
}
// This method creates a 4x4 matrix of Description structures. In the call to parallel_for_each, the structure is updated
// with tile, global, and local indices.
void TilingDescription() {
// Create 16 (4x4) Description structures.
std::vector<Description> descs;
for (int i = 0; i < ROWS * COLS; i++) {
Description d = {i, 0, 0, 0, 0, 0, 0};
descs.push_back(d);
}
// Create an array_view from the Description structures.
extent<2> matrix(ROWS, COLS);
array_view<Description, 2> descriptions(matrix, descs);
// Update each Description with the tile, global, and local indices.
parallel_for_each(descriptions.extent.tile< 2, 3>(),
[= ] (tiled_index< 2, 3> t_idx) restrict(amp)
{
descriptions[t_idx].globalRow = t_idx.global[0];
descriptions[t_idx].globalColumn = t_idx.global[1];
descriptions[t_idx].tileRow = t_idx.tile[0];
descriptions[t_idx].tileColumn = t_idx.tile[1];
descriptions[t_idx].localRow = t_idx.local[0];
descriptions[t_idx].localColumn= t_idx.local[1];
});
// Print out the Description structure for each element in the matrix.
// Tiles are displayed in red and green to distinguish them from each other.
SetConsoleSize(100, 150);
for (int row = 0; row < ROWS; row++) {
for (int column = 0; column < COLS; column++) {
SetConsoleColor((descriptions(row, column).tileRow + descriptions(row, column).tileColumn) % 2);
std::cout << "Value: " << std::setw(2) << descriptions(row, column).value << " ";
}
std::cout << "\n";
for (int column = 0; column < COLS; column++) {
SetConsoleColor((descriptions(row, column).tileRow + descriptions(row, column).tileColumn) % 2);
std::cout << "Tile: " << "(" << descriptions(row, column).tileRow << "," << descriptions(row, column).tileColumn << ") ";
}
std::cout << "\n";
for (int column = 0; column < COLS; column++) {
SetConsoleColor((descriptions(row, column).tileRow + descriptions(row, column).tileColumn) % 2);
std::cout << "Global: " << "(" << descriptions(row, column).globalRow << "," << descriptions(row, column).globalColumn << ") ";
}
std::cout << "\n";
for (int column = 0; column < COLS; column++) {
SetConsoleColor((descriptions(row, column).tileRow + descriptions(row, column).tileColumn) % 2);
std::cout << "Local: " << "(" << descriptions(row, column).localRow << "," << descriptions(row, column).localColumn << ") ";
}
std::cout << "\n";
std::cout << "\n";
}
}
void main() {
TilingDescription();
char wait;
std::cin >> wait;
}
例の主な作業の定義では、 array_viewオブジェクトと呼び出しparallel_for_each。
ベクトルのDescription 、8 x 9 に構造体をコピー array_viewオブジェクト。
parallel_for_eachで、メソッドが呼び出される、 tiled_extentとして計算ドメイン オブジェクト。tiled_extentオブジェクトを呼び出すことにより作成、 extent::tile()のdescriptions変数。型パラメーターの呼び出しをextent::tile()、 <2,3>、2 × 3 のタイルが作成されることを指定します。したがって、8 x 9 の行列は、12 のタイル、4 つの行と 3 つの列に並べて表示されます。
parallel_for_eachメソッドが呼び出されたを使用して、 **tiled_index<2,3>**オブジェクト (t_idx) とインデックス。インデックスの型パラメーター (t_idx)、型パラメーターの計算のドメインと一致する必要があります (descriptions.extent.tile< 2, 3>())。
各スレッドが実行されると、インデックスt_idxスレッドはタイルに関する情報を返します (tiled_index::tileプロパティ) とスレッド内のタイルの位置 (tiled_index::localプロパティ)。
並べて表示の同期: tile_static と tile_barrier::wait
前の例で、牌の配置とインデックスは自体には非常に便利ではありません。タイルが不可欠なは、アルゴリズムと脆弱性を悪用するものとは、タイリングに便利tile_static変数。タイル内のすべてのスレッドにアクセスするためtile_staticへの呼び出しの変数、 tile_barrier::waitへのアクセスを同期するために使用、 tile_static変数。タイル内のスレッドのすべてへのアクセスですが、 tile_static変数のタイル内のスレッドの実行順序が保証はありません。次の使用例を使用する方法を示していますtile_static変数とtile_barrier::waitタイルの平均値を計算する方法。キーの例を理解するのには、次のとおりです。
RawData は 8 × 8 の行列に格納されています。
タイルのサイズは、2 × 2 です。これは 4 × 4 のグリッドのタイルを作成しを使用して、4 × 4 行列の平均値を格納できる、 arrayオブジェクト。ストレージ ・ システムによって制限されている関数で参照して取り込むことができますの種類の数には制限されます。arrayクラスはそれらの 1 つです。
行列のサイズとサンプル ・ サイズを使用して定義されている#defineステートメント、ため、型パラメーターにarray、 array_view、 extent、およびtiled_index定数値である必要があります。使用することもできますconst int static宣言します。その他の利点としては、平均で 4 x 4 のタイルを計算するには、サンプルのサイズを変更するは簡単です。
A tile_static各タイルの 2 x 2 float 値の配列を宣言します。宣言コードのパスにすべてのスレッドが、マトリックス内の各タイルを 1 つだけの配列が作成されます。
各タイルを値をコピーするには、コードの行は、 tile_staticの配列。値、配列にコピーすると各スレッドの実行のスレッドへの呼び出しのために停止tile_barrier::wait。
すべてのスレッドでは、タイル、バリアに到達すると、平均値を計算できます。すべてのスレッドのコードを実行するため、 ifステートメントはのみ 1 つのスレッドの平均を計算します。平均は、平均変数に格納されます。使用可能性がありますと同様、バリアをタイルでは、計算の制御構造は本質的にです、 forループします。
データは、 averages変数では、これであるため、 arrayオブジェクトをホストにコピーする必要があります。次の使用例は、ベクトルの変換演算子を使用します。
完全な例では、SAMPLESIZE を 4 に変更することができ、他の変更を行わずにコードが正しく実行されます。
#include <iostream>
#include <amp.h>
using namespace concurrency;
#define SAMPLESIZE 2
#define MATRIXSIZE 8
void SamplingExample() {
// Create data and array_view for the matrix.
std::vector<float> rawData;
for (int i = 0; i < MATRIXSIZE * MATRIXSIZE; i++) {
rawData.push_back((float)i);
}
extent<2> dataExtent(MATRIXSIZE, MATRIXSIZE);
array_view<float, 2> matrix(dataExtent, rawData);
// Create the array for the averages.
// There is one element in the output for each tile in the data.
std::vector<float> outputData;
int outputSize = MATRIXSIZE / SAMPLESIZE;
for (int j = 0; j < outputSize * outputSize; j++) {
outputData.push_back((float)0);
}
extent<2> outputExtent(MATRIXSIZE / SAMPLESIZE, MATRIXSIZE / SAMPLESIZE);
array<float, 2> averages(outputExtent, outputData.begin(), outputData.end());
// Use tiles that are SAMPLESIZE x SAMPLESIZE.
// Find the average of the values in each tile.
// The only reference-type variable you can pass into the parallel_for_each call
// is a concurrency::array.
parallel_for_each(matrix.extent.tile<SAMPLESIZE, SAMPLESIZE>(),
[=, &averages] (tiled_index<SAMPLESIZE, SAMPLESIZE> t_idx) restrict(amp)
{
// Copy the values of the tile into a tile-sized array.
tile_static float tileValues[SAMPLESIZE][SAMPLESIZE];
tileValues[t_idx.local[0]][t_idx.local[1]] = matrix[t_idx];
// Wait for the tile-sized array to load before you calculate the average.
t_idx.barrier.wait();
// If you remove the if statement, then the calculation executes for every
// thread in the tile, and makes the same assignment to averages each time.
if (t_idx.local[0] == 0 && t_idx.local[1] == 0) {
for (int trow = 0; trow < SAMPLESIZE; trow++) {
for (int tcol = 0; tcol < SAMPLESIZE; tcol++) {
averages(t_idx.tile[0],t_idx.tile[1]) += tileValues[trow][tcol];
}
}
averages(t_idx.tile[0],t_idx.tile[1]) /= (float) (SAMPLESIZE * SAMPLESIZE);
}
});
// Print out the results.
// You cannot access the values in averages directly. You must copy them
// back to a CPU variable.
outputData = averages;
for (int row = 0; row < outputSize; row++) {
for (int col = 0; col < outputSize; col++) {
std::cout << outputData[row*outputSize + col] << " ";
}
std::cout << "\n";
}
// Output for SAMPLESSIZE = 2 is:
// 4.5 6.5 8.5 10.5
// 20.5 22.5 24.5 26.5
// 36.5 38.5 40.5 42.5
// 52.5 54.5 56.5 58.5
// Output for SAMPLESIZE = 4 is:
// 13.5 17.5
// 45.5 49.5
}
int main() {
SamplingExample();
}
競合状態
作成される可能性があります、 tile_staticという名前の変数totalとその各スレッドが、このような変数をインクリメントします。
// Do not do this.
tile_static float total;
total += matrix[t_idx];
t_idx.barrier.wait();
averages(t_idx.tile[0],t_idx.tile[1]) /= (float) (SAMPLESIZE * SAMPLESIZE);
このアプローチの 1 つの問題はtile_static変数初期化子を持つことはできません。2 つ目の問題は、競合状態、割り当てにですtotal、すべてのスレッドがタイルでない特定の順序で、変数へのアクセスを持つため。のみ次に示すように、合計では、それぞれの障壁へのアクセスには、1 つのスレッドを許可するためのアルゴリズムをプログラムすることができます。ただし、このソリューションは拡張可能です。
// Do not do this.
tile_static float total;
if (t_idx.local[0] == 0 && t_idx.local[1] == 0) {
total = matrix[t_idx];
}
t_idx.barrier.wait();
if (t_idx.local[0] == 0 && t_idx.local[1] == 1) {
total += matrix[t_idx];
}
t_idx.barrier.wait();
// etc.
メモリの囲い
2 種類のメモリのアクセスを同期する必要があります: グローバル ・ メモリへのアクセスとtile_staticメモリ アクセス。A concurrency::arrayオブジェクトは、グローバル メモリのみを割り当てます。A concurrency::array_viewグローバル ・ メモリを参照することができますtile_staticメモリ、またはその両方を構築した方法によって異なります。同期する必要がありますメモリの 2 種類です。
グローバル ・ メモリ
tile_static
A メモリ フェンスへのアクセスが他のスレッドがスレッドのタイルに使用可能なメモリとメモリへのアクセスが、プログラムの順序に従って実行されます。これを確認するには、コンパイラとプロセッサの読み取りと書き込みがフェンスにわたって並べ替えないでください。C++ ストレージ ・ システムでは、これらのメソッドのいずれかへの呼び出しによって、メモリ フェンスが作成されます。
tile_barrier::wait メソッド: 両方の周りのフェンスをグローバルに作成し、 tile_staticメモリ。
tile_barrier::wait_with_all_memory_fence メソッド: 両方の周りのフェンスをグローバルに作成し、 tile_staticメモリ。
tile_barrier::wait_with_global_memory_fence メソッド: グローバル メモリのみの周りのフェンスを作成します。
tile_barrier::wait_with_tile_static_memory_fence メソッド: フェンスの周りだけを作成するtile_staticメモリ。
アプリケーションのパフォーマンスを向上することができます必要な特定のフェンスを呼び出しています。どのステートメントの順序を変更、コンパイラ、ハードウェア、バリアのタイプによって異なります。たとえば、グローバル メモリのフェンスを使用してのみにグローバル ・ メモリへのアクセスに適用されます、コンパイラ、ハードウェア、順序を変更可能性があり、読み書きにtile_static変数のフェンスの両側に。
次の例では、バリアーへの書き込みの同期tileValues、 tile_static変数。この例では、 tile_barrier::wait_with_tile_static_memory_fenceの代わりに呼び出されるtile_barrier::wait。
// Using a tile_static memory fence.
parallel_for_each(matrix.extent.tile<SAMPLESIZE, SAMPLESIZE>(),
[=, &averages] (tiled_index<SAMPLESIZE, SAMPLESIZE> t_idx) restrict(amp)
{
// Copy the values of the tile into a tile-sized array.
tile_static float tileValues[SAMPLESIZE][SAMPLESIZE];
tileValues[t_idx.local[0]][t_idx.local[1]] = matrix[t_idx];
// Wait for the tile-sized array to load before calculating the average.
t_idx.barrier.wait_with_tile_static_memory_fence();
// If you remove the if statement, then the calculation executes for every
// thread in the tile, and makes the same assignment to averages each time.
if (t_idx.local[0] == 0 && t_idx.local[1] == 0) {
for (int trow = 0; trow < SAMPLESIZE; trow++) {
for (int tcol = 0; tcol < SAMPLESIZE; tcol++) {
averages(t_idx.tile[0],t_idx.tile[1]) += tileValues[trow][tcol];
}
}
averages(t_idx.tile[0],t_idx.tile[1]) /= (float) (SAMPLESIZE * SAMPLESIZE);
}
});