Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Du kan använda tiling för att maximera accelerationen av din app. Tiling delar upp trådar i lika rektangulära delmängder eller paneler. Om du använder en lämplig panelstorlek och en kaklad algoritm kan du få ännu mer acceleration från din C++ AMP-kod. De grundläggande komponenterna i plattsättning är:
tile_staticVariabler. Den primära fördelen med tiling är prestandavinsten fråntile_staticåtkomsten. Åtkomsten till data itile_staticminnet kan vara betydligt snabbare än åtkomsten till data i det globala utrymmet (arrayellerarray_viewobjekt). En instans av entile_staticvariabel skapas för varje panel och alla trådar i panelen har åtkomst till variabeln. I en typisk tiled-algoritm kopieras data tilltile_staticminnet en gång från det globala minnet och används sedan många gånger fråntile_staticminnet.tile_barrier::wait-metoden. Ett anrop till
tile_barrier::waitavbryter körningen av den aktuella tråden tills alla trådar i samma tile nårtile_barrier::wait. Du kan inte garantera den ordning som trådarna körs i, bara att inga trådar i panelen körs förbi anropet tilltile_barrier::waitförrän alla trådar har nått anropet. Det innebär att du med hjälptile_barrier::waitav metoden kan utföra uppgifter på panel-för-panel-basis i stället för tråd för tråd. En typisk tiling-algoritm har kod för att initieratile_staticminnet för hela panelen följt av ett anrop tilltile_barrier::wait. Koden som följertile_barrier::waitinnehåller beräkningar som kräver åtkomst till allatile_staticvärden.Lokal och global indexering. Du har åtkomst till trådindexet i förhållande till hela
array_viewobjektet ellerarrayobjektet och indexet i förhållande till panelen. Med hjälp av det lokala indexet kan koden bli enklare att läsa och felsöka. Vanligtvis använder du lokal indexering för att komma åttile_staticvariabler och global indexering för åtkomstarrayocharray_viewvariabler.tiled_extent klass och tiled_index klass. Du använder ett
tiled_extentobjekt i stället för ettextentobjekt i anropetparallel_for_each. Du använder etttiled_indexobjekt i stället för ettindexobjekt i anropetparallel_for_each.
För att kunna dra nytta av tiling måste algoritmen partitionera beräkningsdomänen i paneler och sedan kopiera paneldata till tile_static variabler för snabbare åtkomst.
Exempel på globala index, tile-och lokala index
Anmärkning
C++ AMP-huvuden är inaktuella från och med Visual Studio 2022 version 17.0.
Om du inkluderar AMP headers uppstår byggfel. Definiera _SILENCE_AMP_DEPRECATION_WARNINGS innan du inkluderar AMP-huvuden för att undvika varningarna.
Följande diagram representerar en 8x9-matris med data som är ordnad i 2x3-paneler.
I följande exempel visas globala, tileade och lokala index för den här tileade matrisen. Ett array_view objekt skapas med hjälp av element av typen Description. Innehåller Description det globala, kakel- och lokala indexet för elementet i matrisen. Koden vid anropet till parallel_for_each sätter värdena för de globala, panel- och lokala indexen för varje element. Resultatet visar värdena i Description strukturerna.
#include <iostream>
#include <iomanip>
#include <Windows.h>
#include <amp.h>
using namespace concurrency;
const int ROWS = 8;
const int 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 an 8x9 matrix of Description structures.
// In the call to parallel_for_each, the structure is updated
// with tile, global, and local indices.
void TilingDescription() {
// Create 72 (8x9) 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";
}
}
int main() {
TilingDescription();
char wait;
std::cin >> wait;
}
Det huvudsakliga arbetet i exemplet finns i definitionen av array_view objektet och anropet till parallel_for_each.
Strukturvektorn
Descriptionkopieras till ett 8x9-objektarray_view.Metoden
parallel_for_eachanropas med etttiled_extentobjekt som beräkningsdomän. Objektettiled_extentskapas genom att anropaextent::tile()metoden för variabelndescriptions. Typparametrarna för anropet tillextent::tile(),<2,3>, anger att 2x3-plattor skapas. Således är 8x9-matrisen uppdelad i 12 delar, fyra rader och tre kolumner.Metoden
parallel_for_eachanropas med hjälp av etttiled_index<2,3>objekt (t_idx) som index. Typparametrarna för indexet (t_idx) måste matcha typparametrarna för beräkningsdomänen (descriptions.extent.tile< 2, 3>()).När varje tråd körs returnerar indexet
t_idxinformation om vilken panel tråden finns i (tiled_index::tileegenskap) och platsen för tråden i panelen (tiled_index::localegenskapen).
Panelsynkronisering – tile_static och tile_barrier::wait
Det föregående exemplet illustrerar panellayouten och indexen, men är inte i sig särskilt användbart. Plattsättning blir användbart när panelerna är integrerade i algoritmen och utnyttjar tile_static variabler. Eftersom alla trådar i en ruta har åtkomst till tile_static-variabler används anrop till tile_barrier::wait för att synkronisera åtkomsten till tile_static-variablerna. Även om alla trådar i en panel har åtkomst till variablerna tile_static finns det ingen garanterad ordning för körning av trådar i panelen. I följande exempel visas hur du använder tile_static variabler och tile_barrier::wait metoden för att beräkna medelvärdet för varje panel. Här är nycklarna för att förstå exemplet:
RawData lagras i en 8x8-matris.
Panelens storlek är 2x2. Detta skapar ett 4x4 rutnät med paneler och medelvärdena kan lagras i en 4x4-matris med hjälp av ett
arrayobjekt. Det finns bara ett begränsat antal typer som du kan samla in med referens i en AMP-begränsad funktion. Klassenarrayär en av dem.Matrisstorleken och exempelstorleken definieras med hjälp
#defineav instruktioner, eftersom typparametrarna tillarray,array_view,extentochtiled_indexmåste vara konstanta värden. Du kan också användaconst int staticdeklarationer. Som ytterligare en fördel är det trivialt att ändra exempelstorleken för att beräkna genomsnittet över 4x4 paneler.En
tile_static2x2-matris med flyttalvärden deklareras för varje panel. Även om deklarationen finns i kodsökvägen för varje tråd skapas bara en matris för varje panel i matrisen.Det finns en kodrad för att kopiera värdena i varje panel till matrisen
tile_static. När värdet har kopierats till matrisen för varje tråd stoppas körningen på tråden på grund av anropet tilltile_barrier::wait.När alla trådar i en panel har nått barriären kan medelvärdet beräknas. Eftersom koden körs för varje tråd finns det en
ifinstruktion som bara beräknar medelvärdet för en tråd. Medelvärdet lagras i genomsnittsvariabeln. Barriären är i princip den konstruktion som styr beräkningar per panel, ungefär som du kan använda enforloop.Datan i variabeln
averages, eftersom det är ettarray-objekt, måste kopieras tillbaka till värden. I det här exemplet används vektorkonverteringsoperatorn.I det fullständiga exemplet kan du ändra SAMPLESIZE till 4 och koden körs korrekt utan några andra ändringar.
#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 SAMPLESIZE = 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();
}
Tävlingsförhållanden
Det kan vara frestande att skapa en tile_static variabel med namnet total och öka variabeln för varje tråd, så här:
// 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);
Det första problemet med den här metoden är att tile_static variabler inte kan ha initialiserare. Det andra problemet är att det finns ett konkurrensvillkor för tilldelningen till total, eftersom alla trådar i panelen har åtkomst till variabeln i ingen särskild ordning. Du kan programmera en algoritm för att endast tillåta att en tråd får åtkomst till summan vid varje barriär, som du ser härnäst. Den här lösningen är dock inte utökningsbar.
// 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.
Minnesstängsel
Det finns två typer av minnesåtkomster som måste synkroniseras – global minnesåtkomst och tile_static minnesåtkomst. Ett concurrency::array objekt allokerar endast globalt minne. En concurrency::array_view kan referera till globalt minne, tile_static minne eller både och, beroende på hur det konstruerades. Det finns två typer av minne som måste synkroniseras:
globalt minne
tile_static
En minnesbarriär säkerställer att minnesåtkomster är tillgängliga för andra trådar i trådklustret och att minnesåtkomster utförs i enlighet med programordningen. Kompilatorer och processorer ordnar inte om läsningar och skrivningar över staketet för att säkerställa detta. I C++ AMP skapas ett minnesstängsel av ett anrop till någon av följande metoder:
tile_barrier::wait-metoden: Skapar en barriär runt både globalt och
tile_staticminne.tile_barrier::wait_with_all_memory_fence-metoden: Skapar ett staket runt både globalt och
tile_staticminne.tile_barrier::wait_with_global_memory_fence-metoden: Skapar ett staket runt endast globalt minne.
tile_barrier::wait_with_tile_static_memory_fence-metod: Skapar ett staket runt endast
tile_staticminne.
Att anropa den specifika gräns som du behöver kan förbättra appens prestanda. Barriärtypen påverkar hur kompilatorn och maskinvaran omordnar satser. Om du till exempel använder ett globalt minnesstängsel gäller det endast för globala minnesåtkomster och därför kan kompilatorn och maskinvaran ändra ordning på läsningar och skrivningar till tile_static variabler på de två sidorna av stängslet.
I nästa exempel synkroniserar barriären skrivningarna till tileValues, en tile_static variabel. I det här exemplet tile_barrier::wait_with_tile_static_memory_fence anropas i stället för 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);
}
});
Se även
C++ AMP (C++ Accelererad massiv parallellitet)
tile_static nyckelord