Bagikan melalui


Menggunakan Petak Peta

Anda dapat menggunakan ubin untuk memaksimalkan akselerasi aplikasi Anda. Ubin membagi utas menjadi subset atau petak peta persegi panjang yang sama. Jika Anda menggunakan ukuran petak peta dan algoritma ubin yang sesuai, Anda bisa mendapatkan lebih banyak akselerasi dari kode AMP C++ Anda. Komponen dasar petak peta adalah:

  • tile_static Variabel. Manfaat utama ubin adalah perolehan performa dari tile_static akses. Akses ke data dalam tile_static memori dapat secara signifikan lebih cepat daripada akses ke data di ruang global (array atau array_view objek). Instans tile_static variabel dibuat untuk setiap petak peta, dan semua utas dalam petak peta memiliki akses ke variabel. Dalam algoritma petak umum, data disalin ke dalam tile_static memori sekali dari memori global dan kemudian diakses berkali-kali dari tile_static memori.

  • tile_barrier::tunggu Metode. Panggilan untuk tile_barrier::wait menangguhkan eksekusi utas saat ini hingga semua utas dalam ubin yang sama mencapai panggilan ke tile_barrier::wait. Anda tidak dapat menjamin urutan yang akan dijalankan utas, hanya saja tidak ada utas dalam petak peta yang akan dijalankan melewati panggilan hingga tile_barrier::wait semua utas mencapai panggilan. Ini berarti bahwa dengan menggunakan metode ini tile_barrier::wait , Anda dapat melakukan tugas berdasarkan petak peta demi peta daripada berdasarkan utas demi utas. Algoritma petak peta yang khas memiliki kode untuk menginisialisasi tile_static memori untuk seluruh petak peta diikuti dengan panggilan ke tile_barrier::wait. Kode berikut berisi tile_barrier::wait komputasi yang memerlukan akses ke semua tile_static nilai.

  • Pengindeksan lokal dan global. Anda memiliki akses ke indeks utas relatif terhadap seluruh array_view atau array objek dan indeks relatif terhadap petak peta. Menggunakan indeks lokal dapat membuat kode Anda lebih mudah dibaca dan di-debug. Biasanya, Anda menggunakan pengindeksan lokal untuk mengakses tile_static variabel, dan pengindeksan global untuk mengakses array dan array_view variabel.

  • Kelas tiled_extent dan Kelas tiled_index. Anda menggunakan tiled_extent objek alih-alih extent objek dalam parallel_for_each panggilan. Anda menggunakan tiled_index objek alih-alih index objek dalam parallel_for_each panggilan.

Untuk memanfaatkan ubin, algoritma Anda harus mempartisi domain komputasi ke dalam petak peta lalu menyalin data petak peta ke dalam tile_static variabel untuk akses yang lebih cepat.

Contoh Indeks Global, Petak, dan Lokal

Catatan

Header AMP C++ tidak digunakan lagi dimulai dengan Visual Studio 2022 versi 17.0. Menyertakan header AMP apa pun akan menghasilkan kesalahan build. Tentukan _SILENCE_AMP_DEPRECATION_WARNINGS sebelum menyertakan header AMP apa pun untuk membungkam peringatan.

Diagram berikut mewakili matriks data 8x9 yang diatur dalam petak peta 2x3.

Diagram 8 dengan 9 matriks dibagi menjadi 2 dengan 3 petak peta.

Contoh berikut menampilkan indeks global, petak peta, dan lokal dari matriks ubin ini. Objek array_view dibuat dengan menggunakan elemen jenis Description. memegang Description indeks global, petak peta, dan lokal elemen dalam matriks. Kode dalam panggilan untuk parallel_for_each mengatur nilai indeks global, petak peta, dan lokal dari setiap elemen. Output menampilkan nilai dalam Description struktur.

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

Pekerjaan utama contoh adalah dalam definisi array_view objek dan panggilan ke parallel_for_each.

  1. Vektor Description struktur disalin ke dalam objek 8x9 array_view .

  2. Metode parallel_for_each ini dipanggil dengan tiled_extent objek sebagai domain komputasi. Objek tiled_extent dibuat dengan memanggil extent::tile() metode descriptions variabel. Parameter jenis panggilan ke extent::tile(), <2,3>, tentukan bahwa petak peta 2x3 dibuat. Dengan demikian, matriks 8x9 diurutkan menjadi 12 petak peta, empat baris dan tiga kolom.

  3. Metode parallel_for_each ini dipanggil dengan menggunakan tiled_index<2,3> objek (t_idx) sebagai indeks. Parameter jenis indeks (t_idx) harus cocok dengan parameter jenis domain komputasi (descriptions.extent.tile< 2, 3>()).

  4. Ketika setiap utas dijalankan, indeks t_idx mengembalikan informasi tentang ubin mana utas berada di (tiled_index::tile properti) dan lokasi utas dalam ubin (tiled_index::local properti).

Sinkronisasi Petak Peta—tile_static dan tile_barrier::tunggu

Contoh sebelumnya menggambarkan tata letak petak peta dan indeks, tetapi tidak dengan sendirinya sangat berguna. Ubin menjadi berguna ketika petak peta terintegrasi dengan algoritma dan mengeksploitasi tile_static variabel. Karena semua utas dalam petak peta memiliki akses ke tile_static variabel, panggilan ke tile_barrier::wait digunakan untuk menyinkronkan akses ke tile_static variabel. Meskipun semua utas dalam petak peta memiliki akses ke tile_static variabel, tidak ada urutan eksekusi utas yang dijamin dalam petak peta. Contoh berikut menunjukkan cara menggunakan tile_static variabel dan tile_barrier::wait metode untuk menghitung nilai rata-rata setiap petak peta. Berikut adalah kunci untuk memahami contoh:

  1. RawData disimpan dalam matriks 8x8.

  2. Ukuran petak peta adalah 2x2. Ini membuat kisi petak peta 4x4 dan rata-rata dapat disimpan dalam matriks 4x4 dengan menggunakan array objek. Hanya ada sejumlah jenis terbatas yang dapat Anda ambil berdasarkan referensi dalam fungsi yang dibatasi AMP. Kelas array adalah salah satunya.

  3. Ukuran matriks dan ukuran sampel didefinisikan dengan menggunakan #define pernyataan, karena parameter jenis ke array, array_view, extent, dan tiled_index harus berupa nilai konstanta. Anda juga dapat menggunakan const int static deklarasi. Sebagai manfaat tambahan, sepele untuk mengubah ukuran sampel untuk menghitung rata-rata lebih dari petak peta 4x4.

  4. Array tile_static 2x2 nilai float dideklarasikan untuk setiap petak peta. Meskipun deklarasi berada di jalur kode untuk setiap utas, hanya satu array yang dibuat untuk setiap petak dalam matriks.

  5. Ada baris kode untuk menyalin nilai di setiap petak peta ke tile_static array. Untuk setiap utas, setelah nilai disalin ke array, eksekusi pada utas berhenti karena panggilan ke tile_barrier::wait.

  6. Ketika semua utas dalam petak peta telah mencapai hambatan, rata-rata dapat dihitung. Karena kode dijalankan untuk setiap utas, ada if pernyataan untuk hanya menghitung rata-rata pada satu utas. Rata-rata disimpan dalam variabel rata-rata. Penghalang pada dasarnya adalah konstruksi yang mengontrol perhitungan berdasarkan petak peta, sebanyak Anda mungkin menggunakan perulangan for .

  7. Data dalam averages variabel, karena merupakan array objek, harus disalin kembali ke host. Contoh ini menggunakan operator konversi vektor.

  8. Dalam contoh lengkap, Anda dapat mengubah SAMPLESIZE menjadi 4 dan kode dijalankan dengan benar tanpa perubahan lain.

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

Kondisi Race

Mungkin menggoda untuk membuat tile_static variabel bernama total dan menaikkan variabel tersebut untuk setiap utas, seperti ini:

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

Masalah pertama dengan pendekatan ini adalah bahwa tile_static variabel tidak dapat memiliki penginisialisasi. Masalah kedua adalah bahwa ada kondisi balapan pada penugasan ke total, karena semua utas dalam ubin memiliki akses ke variabel dalam urutan tertentu. Anda dapat memprogram algoritma untuk hanya mengizinkan satu utas mengakses total di setiap hambatan, seperti yang ditunjukkan berikutnya. Namun, solusi ini tidak dapat diperluas.

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

Pagar Memori

Ada dua jenis akses memori yang harus disinkronkan—akses memori global dan tile_static akses memori. Objek concurrency::array hanya mengalokasikan memori global. Dapat concurrency::array_view mereferensikan memori global, tile_static memori, atau keduanya, tergantung pada bagaimana itu dibangun. Ada dua jenis memori yang harus disinkronkan:

  • memori global

  • tile_static

Pagar memori memastikan bahwa akses memori tersedia untuk utas lain di petak peta utas, dan bahwa akses memori dijalankan sesuai dengan urutan program. Untuk memastikan hal ini, pengkompilasi dan prosesor tidak menyusun ulang baca dan tulis di seluruh pagar. Di C++ AMP, pagar memori dibuat oleh panggilan ke salah satu metode ini:

Memanggil pagar tertentu yang Anda butuhkan dapat meningkatkan performa aplikasi Anda. Jenis pembatas memengaruhi bagaimana kompilator dan pernyataan pengulangan perangkat keras. Misalnya, jika Anda menggunakan pagar memori global, itu hanya berlaku untuk akses memori global dan oleh karena itu, pengkompilasi dan perangkat keras mungkin menyusun ulang baca dan tulis ke tile_static variabel di dua sisi pagar.

Dalam contoh berikutnya, hambatan menyinkronkan penulisan ke tileValues, tile_static variabel. Dalam contoh ini, tile_barrier::wait_with_tile_static_memory_fence dipanggil alih-alih 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);
    }
});

Lihat juga

C++ AMP (C++ Paralelisme Masif Dipercepat)
kata kunci tile_static