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. Tiling membagi utas menjadi subset persegi panjang yang sama atau petak. Jika Anda menggunakan ukuran petak dan algoritma ubin yang sesuai, Anda bisa mendapatkan lebih banyak akselerasi dari kode C++ AMP Anda. Komponen dasar penyusunan ubin 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 thread saat ini hingga semua thread dalam kumpulan yang sama mencapai panggilan ketile_barrier::wait. Anda tidak dapat menjamin urutan eksekusi utas, hanya saja tidak ada utas dalam tile yang akan dijalankan melebihi panggilan ketile_barrier::waithingga semua utas mencapai panggilan tersebut. 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 yang mengikutitile_barrier::waitmengandung komputasi yang memerlukan akses terhadap semua nilaitile_static.Pengindeksan lokal dan global. Anda memiliki akses ke indeks utas relatif terhadap seluruh
array_viewatauarrayobjek dan indeks relatif terhadap ubin. 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 objek
tiled_extentalih-alih objekextentdalam panggilanparallel_for_each. Anda menggunakan objektiled_indexalih-alih objekindexdalam panggilanparallel_for_each.
Untuk memanfaatkan fungsi tiling, algoritma Anda harus mempartisi domain komputasi menjadi ubin-ubin lalu menyalin data ubin ke dalam tile_static variabel untuk akses yang lebih cepat.
Contoh Indeks Global, Petak, dan Lokal
Nota
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.
Description menyimpan indeks global, petak, 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-nilai dalam struktur Description.
#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 dibagi menjadi 12 petak, 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 dieksekusi, indeks
t_idxmengembalikan informasi tentang ubin tempat utas berada (tiled_index::tileproperti) dan lokasi utas dalam ubin (tiled_index::localproperti).
Sinkronisasi Tile—tile_static dan tile_barrier::wait
Contoh sebelumnya menggambarkan tata letak ubin dan indeks, tetapi tidak dengan sendirinya sangat berguna. Ubin menjadi berguna ketika ubin tersebut merupakan bagian integral dari algoritma dan mengeksploitasi tile_static variabel. Karena semua utas dalam petak memiliki akses ke variabel tile_static, panggilan ke tile_barrier::wait digunakan untuk menyinkronkan akses ke variabel tile_static. Meskipun semua utas dalam petak dapat mengakses variabel tile_static, tidak ada urutan eksekusi utas yang dijamin dalam petak tersebut. 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 objek
array. 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 menggunakan deklarasiconst int static. Sebagai manfaat tambahan, sangat mudah untuk mengubah ukuran sampel guna menghitung rata-rata pada ubin 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 elemen 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 tile 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 per petak, mirip seperti Anda 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 Balapan
Mungkin tergoda untuk membuat tile_static variabel bernama total dan meningkatkan nilai variabel tersebut untuk setiap thread, 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 lomba pada penugasan ke total, karena semua utas dalam ubin mengakses variabel tersebut tidak dalam urutan tertentu. Anda dapat memprogram algoritma untuk hanya mengizinkan satu utas mengakses total di setiap hambatan, seperti yang ditunjukkan berikut ini. 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.
Batas Memori
Ada dua jenis akses memori yang harus disinkronkan—akses memori global dan tile_static akses memori. Objek concurrency::array hanya mengalokasikan memori global. Sebuah concurrency::array_view dapat mereferensikan memori global, memori tile_static, atau keduanya, tergantung pada bagaimana ia 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 pembacaan dan penulisan melintasi batas. Di C++ AMP, pagar memori dibuat oleh panggilan ke salah satu metode ini:
tile_barrier::wait Method: Membuat penghalang di sekitar memori global dan
tile_static.tile_barrier::wait_with_all_memory_fence Method: Membuat barier di sekitar memori global dan
tile_static.tile_barrier::wait_with_global_memory_fence Method: Membuat pagar memori secara khusus di sekitar memori global.
tile_barrier::wait_with_tile_static_memory_fence Method: Membuat pembatas hanya di sekitar memori.
Memanggil pagar tertentu yang Anda butuhkan dapat meningkatkan performa aplikasi Anda. Jenis pembatas memengaruhi bagaimana kompilator dan perangkat keras menyusun ulang perintah. 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 operasi baca dan tulis ke tile_static variabel di kedua sisi pagar.
Dalam contoh berikutnya, penghalang menyinkronkan penulisan ke tileValues, variabel tile_static. 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);
}
});
Baca juga
C++ AMP (C++ Paralelisme Masif Dipercepat)
kata kunci tile_static