Dela via


Använda brickor

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_static Variabler. Den primära fördelen med tiling är prestandavinsten från tile_static åtkomsten. Åtkomsten till data i tile_static minnet kan vara betydligt snabbare än åtkomsten till data i det globala utrymmet (array eller array_view objekt). En instans av en tile_static variabel skapas för varje panel och alla trådar i panelen har åtkomst till variabeln. I en typisk tiled-algoritm kopieras data till tile_static minnet en gång från det globala minnet och används sedan många gånger från tile_static minnet.

  • tile_barrier::wait-metoden. Ett anrop till tile_barrier::wait avbryter körningen av den aktuella tråden tills alla trådar i samma tile når tile_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 till tile_barrier::wait förrän alla trådar har nått anropet. Det innebär att du med hjälp tile_barrier::wait av 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 initiera tile_static minnet för hela panelen följt av ett anrop till tile_barrier::wait. Koden som följer tile_barrier::wait innehåller beräkningar som kräver åtkomst till alla tile_static värden.

  • Lokal och global indexering. Du har åtkomst till trådindexet i förhållande till hela array_view objektet eller array objektet 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 åt tile_static variabler och global indexering för åtkomst array och array_view variabler.

  • tiled_extent klass och tiled_index klass. Du använder ett tiled_extent objekt i stället för ett extent objekt i anropet parallel_for_each . Du använder ett tiled_index objekt i stället för ett index objekt i anropet parallel_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.

Diagram över en 8 x 9 matris uppdelad i 2 x 3 fält.

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.

  1. Strukturvektorn Description kopieras till ett 8x9-objekt array_view .

  2. Metoden parallel_for_each anropas med ett tiled_extent objekt som beräkningsdomän. Objektet tiled_extent skapas genom att anropa extent::tile() metoden för variabeln descriptions . Typparametrarna för anropet till extent::tile(), <2,3>, anger att 2x3-plattor skapas. Således är 8x9-matrisen uppdelad i 12 delar, fyra rader och tre kolumner.

  3. Metoden parallel_for_each anropas med hjälp av ett tiled_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>()).

  4. När varje tråd körs returnerar indexet t_idx information om vilken panel tråden finns i (tiled_index::tile egenskap) och platsen för tråden i panelen (tiled_index::local egenskapen).

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:

  1. RawData lagras i en 8x8-matris.

  2. 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 array objekt. Det finns bara ett begränsat antal typer som du kan samla in med referens i en AMP-begränsad funktion. Klassen array är en av dem.

  3. Matrisstorleken och exempelstorleken definieras med hjälp #define av instruktioner, eftersom typparametrarna till array, array_view, extentoch tiled_index måste vara konstanta värden. Du kan också använda const int static deklarationer. Som ytterligare en fördel är det trivialt att ändra exempelstorleken för att beräkna genomsnittet över 4x4 paneler.

  4. En tile_static 2x2-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.

  5. 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 till tile_barrier::wait.

  6. 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 if instruktion 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 en for loop.

  7. Datan i variabeln averages, eftersom det är ett array-objekt, måste kopieras tillbaka till värden. I det här exemplet används vektorkonverteringsoperatorn.

  8. 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:

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