Share via


Verwenden von Kacheln

Kacheln können Sie um die Beschleunigung Ihrer Anwendung zu maximieren.Kacheln unterteilt Threads in gleich rechteckigen Teilmengen oder Fliesen.Wenn Sie eine entsprechende Kachelgröße und gekachelte Algorithmus verwenden, erhalten Sie noch mehr Beschleunigung aus dem AMP für C++-Code.Die grundlegenden Komponenten der Kacheln sind:

  • tile_staticVariablen.Der Hauptvorteil der Kacheln ist der Leistungsgewinn in tile_static Zugriff.Zugriff auf Daten in tile_static Memory kann erheblich schneller als der Zugriff auf Daten in den globalen Raum (array oder array_view Objekte).Eine Instanz einer tile_static Variable wird für jede Kachel erstellt, und alle Threads in der Kachel haben Zugriff auf die Variable.In einem typischen gekachelte Algorithmus Daten kopiert, in tile_static aus dem globalen Speicher einmal und dann oft von der tile_static Speicher.

  • tile_barrier::wait-Methode.Ein Aufruf von tile_barrier::wait unterbricht die Ausführung des aktuellen Threads bis alle Threads in der gleichen Fläche erreichen, dass den Aufruf von tile_barrier::wait.Es kann nicht garantiert, dass die Reihenfolge, die die Threads im ausgeführt werden, nur das keine Threads in der Kachel führt nach dem Aufruf von tile_barrier::wait , bis alle Threads den Aufruf erreicht haben.Dies bedeutet, dass mithilfe der tile_barrier::wait -Methode, Sie können Aufgaben auf einer Kachel von Kachel Basis anstatt einen Thread von Thread-Basis.Ein typische Unterteilung-Algorithmus hat Code zum Initialisieren der tile_static Speicher für die gesamte Kachel gefolgt durch einen Aufruf von tile_barrer::wait.Der folgende Code tile_barrier::wait enthält Berechnungen, die Zugriff auf alle erfordern die tile_static Werte.

  • Lokale und globale zu indizieren.Sie haben Zugriff auf den Index des Threads bezogen auf das gesamte array_view oder array -Objekt und der Index relativ zu der Kachel.Mithilfe des lokalen Indexes kann Ihr Code leichter zu lesen und zu debuggen.Normalerweise Sie verwenden Access lokale Indizierung tile_static Variablen und globale Indizierung Access array und array_view Variablen.

  • tiled_extent-Klasse und tiled_index-Klasse.Sie verwenden ein tiled_extent anstelle des Objekts ein extent -Objekt in der parallel_for_each aufrufen.Sie verwenden ein tiled_index anstelle des Objekts ein index -Objekt in der parallel_for_each aufrufen.

Um Fliesen nutzen zu können, muss Ihr Algorithmus partition die Compute-Domäne in Kacheln unterteilt und kopieren Sie die Tile-Daten in tile_static Variablen für einen schnelleren Zugriff.

Beispiel für globale, Fliesen und lokale Indizes

Das folgende Diagramm stellt eine 8 x 9 Matrix der Daten, die in 2 x 3 Fliesen angeordnet ist.

Eine in 2x3 Kacheln unterteilte 8x9-Matrix

Das folgende Beispiel zeigt die globale, Kachel und lokale Indizes gekachelt Matrix.Ein array_view Objekt wird erstellt, indem Sie Elemente vom Typ Description.Die Description enthält die globalen Kachel und lokale Indizes des Elements in der Matrix.Der Code im Aufruf von parallel_for_each Werte der globalen, Kachel und lokale Indizes des jeweiligen Elements festgelegt.Die Ausgabe zeigt die Werte in der Description Strukturen.

#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;
}

Die eigentliche Arbeit des Beispiels ist in der Definition der array_view -Objekt und dem Aufruf von parallel_for_each.

  1. Der Vektor der Description Strukturen wird in ein 8 x 9 kopiert array_view Objekt.

  2. Die parallel_for_each -Methode wird aufgerufen, mit einem tiled_extent Objekt wie die Compute-Domäne.Die tiled_extent -Objekt wird erstellt, durch Aufrufen der extent::tile() Methode der descriptions Variable.Die Type-Parameter des Aufrufs von extent::tile(), <2,3>, angeben, dass die 2 x 3 Kacheln erstellt werden.Folglich wird die Matrix 8 x 9 in 12 Steine, vier Zeilen und drei Spalten angeordnet.

  3. Die parallel_for_each -Methode wird aufgerufen, mit einem tiled_index<2,3> Objekt (t_idx) als Index.Die Type-Parameter des Index (t_idx) die Typparameter der Compute-Domäne muss übereinstimmen (descriptions.extent.tile< 2, 3>()).

  4. Wenn jeder Thread ausgeführt wird, der Index t_idx gibt Informationen über die Kachel der Thread befindet sich im (tiled_index::tile Eigenschaft) und den Speicherort des Threads innerhalb des Felds (tiled_index::local Eigenschaft).

Tile Synchronisation — Tile_static und tile_barrier::wait

Im vorherige Beispiel veranschaulicht die Kartenlayout und Indizes, aber nicht an sich sehr nützlich ist.Fliesen wird nützlich, wenn die Fliesen integraler Bestandteil der Algorithmus und Exploit tile_static Variablen.Da alle Threads in einer Kachel Zugriff auf tile_static Variablen, die Aufrufe von tile_barrier::wait dienen zum Synchronisieren des Zugriffs auf die tile_static Variablen.Auch wenn alle Threads in einer Kachel Zugriff auf die tile_static Variablen, gibt es keine festgelegte Reihenfolge der Ausführung von Threads in der Kachel.Im folgenden Beispiel wird veranschaulicht, wie mithilfe von tile_static Variablen und die tile_barrier::wait Methode zur Berechnung des durchschnittlichen Wertes Ziegel.Hier sind die Schlüssel für das Verständnis des im Beispiel:

  1. Der RawData wird in ein 8 x 8 Matrix gespeichert.

  2. Die Größe die Kacheln ist 2 x 2.Dies erstellt ein 4 x 4-Raster der Fliesen und die Durchschnittswerte können in einer 4 x 4 Matrix gespeichert werden, mithilfe einer array Objekt.Es gibt nur eine begrenzte Anzahl von Typen, die als Verweis in einer EVA-eingeschränkte Funktion erfasst werden können.Die array Klasse ist einer von ihnen.

  3. Die Matrixgröße und Umfang der Stichprobe mithilfe definiert #define Anweisungen, da die Typparameter zu array, array_view, extent, und tiled_index Konstante Werte sein müssen.Sie können auch const int static Erklärungen.Ein weiterer Vorteil ist es sehr einfach zu ändern, die Stichprobengröße, um durchschnittlichen mehr als 4 x 4 Kacheln zu berechnen.

  4. A tile_static 2 x 2-Array von Float-Werte für jede Kachel deklariert ist.Obwohl die Deklaration in dem Codepfad für jeden Thread ist, wird für jede Kachel in der Matrix nur ein Array erstellt.

  5. Gibt es eine Zeile Code, um die Werte in jeder Kachel zum Kopieren der tile_static Array.Für jeden Thread, nachdem der Wert in das Array kopiert wird Ausführung des Threads beendet aufgrund der Aufruf von tile_barrier::wait.

  6. Wenn alle Threads in einer Kachel die Barriere erreicht haben, kann der Mittelwert berechnet werden.Da der Code für jeden Thread ausgeführt wird, gibt es ein if -Anweisung, um den Mittelwert für einen Thread nur zu berechnen.Der Durchschnitt ist in der Durchschnittswerte-Variablen gespeichert.Die Barriere ist im Wesentlichen das Konstrukt, die Berechnungen in der Kachel, steuert, genauso, als Sie vielleicht eine for Schleife.

  7. Die Daten in der averages Variablen, denn es ist ein array Objekt, mit dem Host kopiert werden müssen.Dieses Beispiel verwendet den Vektor-Konvertierungsoperator.

  8. Im vollständigen Beispiel können Sie SAMPLESIZE auf 4 und der Code wird ohne andere Änderungen ordnungsgemäß ausgeführt.

#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();
}

Racebedingungen

Es könnte verlockend sein, erstellen Sie eine tile_static Variable mit dem Namen total und die Variable für jeden Thread wie folgt erhöhen:

// 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);

Das erste Problem bei diesem Ansatz ist, die tile_static Variablen keinen Initialisierer.Das zweite Problem ist, dass es eine Race-Bedingung für die Zuordnung zu total, da alle Threads in der Kachel Zugriff auf die Variable in keiner bestimmten Reihenfolge haben.Sie könnten einen Algorithmus, um nur ein Thread auf die Summe an jede Barriere zulassen, wie nachfolgend dargestellt programmieren.Diese Lösung ist jedoch nicht erweiterbar.

// 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.

Speicher-Zäune

Es gibt zwei Arten von Speicherzugriffe, die synchronisiert werden müssen – Zugriff auf den globalen Speicher und tile_static Zugriff auf den Speicher.A concurrency::array Objekt reserviert nur globale Speicher.A concurrency::array_view können global Memory verweisen tile_static Speicher oder beides, je nachdem, wie es erstellt wurde.Es gibt zwei Arten von Arbeitsspeicher, die synchronisiert werden müssen:

  • globaler Speicher

  • tile_static

A -Arbeitsspeicher Zaun wird sichergestellt, dass Zugriffe auf andere Threads in der Kachel Thread verfügbar sind und diese Arbeitsspeicher Zugriffe entsprechend programmreihenfolge ausgeführt werden.Um dies zu gewährleisten, sind Compiler und Prozessoren nicht Lese- und Schreibvorgänge über den Zaun neu an.In C++ AMP wird ein Arbeitsspeicher-Zaun durch einen Aufruf einer der folgenden Methoden erstellt:

Aufrufen des spezifischen Zauns, die Sie benötigen die Leistung Ihrer Anwendung verbessern können.Barriere Typ wirkt sich die Compiler und die Hardware wie Anweisungen neu anordnen.Beispielsweise, wenn Sie einen Zaun global Memory, sich nur auf die globalen Speicherzugriffe bezieht und daher der Compiler und die Hardware neu ordnen, liest und schreibt in tile_static Variablen auf beiden Seiten des Zauns.

Im nächsten Beispiel Talsperre synchronisiert die Schreibvorgänge zum tileValues, ein tile_static Variable.In diesem Beispiel tile_barrier::wait_with_tile_static_memory_fence wird, wird statt 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);
    }
});

Siehe auch

Referenz

tile_static Schlüsselwort

Weitere Ressourcen

C++ AMP (C++ Accelerated Massive Parallelism)