Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
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_staticVariabel. Manfaat utama ubin adalah perolehan performa daritile_staticakses. Akses ke data dalamtile_staticmemori dapat secara signifikan lebih cepat daripada akses ke data di ruang global (arrayatauarray_viewobjek). Instanstile_staticvariabel dibuat untuk setiap petak peta, dan semua utas dalam petak peta memiliki akses ke variabel. Dalam algoritma petak umum, data disalin ke dalamtile_staticmemori sekali dari memori global dan kemudian diakses berkali-kali daritile_staticmemori.tile_barrier::tunggu Metode. Panggilan untuk
tile_barrier::waitmenangguhkan eksekusi utas saat ini hingga semua utas dalam ubin yang sama mencapai panggilan ketile_barrier::wait. Anda tidak dapat menjamin urutan yang akan dijalankan utas, hanya saja tidak ada utas dalam petak peta yang akan dijalankan melewati panggilan hinggatile_barrier::waitsemua utas mencapai panggilan. Ini berarti bahwa dengan menggunakan metode initile_barrier::wait, Anda dapat melakukan tugas berdasarkan petak peta demi peta daripada berdasarkan utas demi utas. Algoritma petak peta yang khas memiliki kode untuk menginisialisasitile_staticmemori untuk seluruh petak peta diikuti dengan panggilan ketile_barrier::wait. Kode berikut berisitile_barrier::waitkomputasi yang memerlukan akses ke semuatile_staticnilai.Pengindeksan lokal dan global. Anda memiliki akses ke indeks utas relatif terhadap seluruh
array_viewatauarrayobjek 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 mengaksestile_staticvariabel, dan pengindeksan global untuk mengaksesarraydanarray_viewvariabel.Kelas tiled_extent dan Kelas tiled_index. Anda menggunakan
tiled_extentobjek alih-alihextentobjek dalamparallel_for_eachpanggilan. Anda menggunakantiled_indexobjek alih-alihindexobjek dalamparallel_for_eachpanggilan.
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.
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.
Vektor
Descriptionstruktur disalin ke dalam objek 8x9array_view.Metode
parallel_for_eachini dipanggil dengantiled_extentobjek sebagai domain komputasi. Objektiled_extentdibuat dengan memanggilextent::tile()metodedescriptionsvariabel. Parameter jenis panggilan keextent::tile(),<2,3>, tentukan bahwa petak peta 2x3 dibuat. Dengan demikian, matriks 8x9 diurutkan menjadi 12 petak peta, empat baris dan tiga kolom.Metode
parallel_for_eachini dipanggil dengan menggunakantiled_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>()).Ketika setiap utas dijalankan, indeks
t_idxmengembalikan informasi tentang ubin mana utas berada di (tiled_index::tileproperti) dan lokasi utas dalam ubin (tiled_index::localproperti).
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:
RawData disimpan dalam matriks 8x8.
Ukuran petak peta adalah 2x2. Ini membuat kisi petak peta 4x4 dan rata-rata dapat disimpan dalam matriks 4x4 dengan menggunakan
arrayobjek. Hanya ada sejumlah jenis terbatas yang dapat Anda ambil berdasarkan referensi dalam fungsi yang dibatasi AMP. Kelasarrayadalah salah satunya.Ukuran matriks dan ukuran sampel didefinisikan dengan menggunakan
#definepernyataan, karena parameter jenis kearray,array_view,extent, dantiled_indexharus berupa nilai konstanta. Anda juga dapat menggunakanconst int staticdeklarasi. Sebagai manfaat tambahan, sepele untuk mengubah ukuran sampel untuk menghitung rata-rata lebih dari petak peta 4x4.Array
tile_static2x2 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.Ada baris kode untuk menyalin nilai di setiap petak peta ke
tile_staticarray. Untuk setiap utas, setelah nilai disalin ke array, eksekusi pada utas berhenti karena panggilan ketile_barrier::wait.Ketika semua utas dalam petak peta telah mencapai hambatan, rata-rata dapat dihitung. Karena kode dijalankan untuk setiap utas, ada
ifpernyataan 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 perulanganfor.Data dalam
averagesvariabel, karena merupakanarrayobjek, harus disalin kembali ke host. Contoh ini menggunakan operator konversi vektor.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:
tile_barrier::wait Method: Membuat pagar di sekitar global dan
tile_staticmemori.tile_barrier::wait_with_all_memory_fence Method: Membuat pagar di sekitar global dan
tile_staticmemori.tile_barrier::wait_with_global_memory_fence Method: Membuat pagar hanya di sekitar memori global.
tile_barrier::wait_with_tile_static_memory_fence Method: Membuat pagar hanya
tile_staticdi sekitar memori.
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