Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Uygulamanızın hızlandırmasını en üst düzeye çıkarmak için döşemeyi kullanabilirsiniz. Döşeme, iş parçacıklarını eşit dikdörtgen alt kümelere veya kutucuklara böler. Uygun bir kutucuk boyutu ve kutucuklu algoritma kullanıyorsanız, C++ AMP kodunuzdan daha da fazla hızlandırma elde edebilirsiniz. Döşemenin temel bileşenleri şunlardır:
tile_staticDeğişken. Döşeme işleminin birincil avantajı,tile_staticerişiminden elde edilen performans artışıdır. Bellektekitile_staticverilere erişim, genel alanda (arrayveyaarray_viewnesnelerde) verilere erişimden önemli ölçüde daha hızlı olabilir. Her bir kutucuk için birtile_staticdeğişken örneği oluşturulur ve kutucuktaki tüm iş parçacıkları değişkene erişebilir. Tipik bir kutucuklu algoritmada veriler genel bellekten bir kez belleğe kopyalanırtile_staticve ardından bellektentile_staticbirçok kez erişilir.tile_barrier::wait Yöntemi.
tile_barrier::waitçağrısı, aynı kutucuktaki tüm iş parçacıklarıtile_barrier::waitçağrısına ulaşana kadar geçerli iş parçacığının yürütülmesini askıya alır. İş parçacıklarının hangi sırada çalışacağını garanti edemezsiniz; ancak, tüm iş parçacıklarıtile_barrier::waitçağrısına ulaşmadan, kutucuktaki hiçbir iş parçacığı çağrının ötesinde yürütülmeyecektir. Bu,tile_barrier::waityöntemini kullanarak görevleri iş parçacığı yerine kutucuk bazında gerçekleştirebileceğiniz anlamına gelir. Tipik bir döşeme algoritması, tüm döşeme içintile_staticbelleğini başlatan kodu ve ardındantile_barrier::waitçağrısı içerir. Aşağıdakitile_barrier::waitkod, tümtile_staticdeğerlere erişim gerektiren hesaplamalar içerir.Yerel ve genel dizin oluşturma. İş parçacığının dizinine, tüm
array_viewveyaarraynesnesine ve karo bazındaki dizine erişiminiz vardır. Yerel dizini kullanmak kodunuzun okunmasını ve hatalarını ayıklamayı kolaylaştırabilir. Genellikle, `tile_static` değişkenlerine erişmek için yerel dizinleme, `array` ve `array_view` değişkenlerine erişmek için genel dizinleme kullanırsınız.Tiled_extent Sınıfı ve tiled_index Sınıfı. Çağrıda
tiled_extentnesne yerine birextentnesne kullanırsınızparallel_for_each. Çağrıdatiled_indexnesne yerine birindexnesne kullanırsınızparallel_for_each.
Döşemeden yararlanmak için algoritmanızın işlem etki alanını kutucuklara ayırması ve daha hızlı erişim için kutucuk verilerini değişkenlere tile_static kopyalaması gerekir.
Genel, Fayans ve Yerel Dizinler örneği
Uyarı
C++ AMP üst bilgileri Visual Studio 2022 sürüm 17.0'dan itibaren kullanım dışı bırakılmıştır.
AMP üst bilgilerini dahil etmek derleme hatalarına neden olur. AMP üst bilgilerini eklemeden önce uyarıları susturmak için _SILENCE_AMP_DEPRECATION_WARNINGS tanımla.
Aşağıdaki diyagram, 2x3 kutucuklar halinde düzenlenmiş 8x9 veri matrisini temsil eder.
Aşağıdaki örnek, bu kutucuklu matrisin genel, kutucuk ve yerel dizinlerini görüntüler. Nesne array_view türündeki Descriptionöğeler kullanılarak oluşturulur. , Description matristeki öğenin genel, kutucuk ve yerel dizinlerini tutar. çağrısındaki parallel_for_each kod, her öğenin genel, kutucuk ve yerel dizinlerinin değerlerini ayarlar. Çıkış, yapılardaki Description değerleri görüntüler.
#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;
}
Örneğin ana işi, array_view nesnesinin tanımında ve parallel_for_each çağrısındadır.
Yapıların vektöru
Descriptionbir 8x9array_viewnesnesine kopyalanır.parallel_for_eachyöntemi, işlem etki alanı olarak birtiled_extentnesneyle çağrılır.tiled_extentnesnesi değişkenininextent::tile()yöntemidescriptionsçağrılarak oluşturulur.extent::tile()çağrısının tür parametreleri,<2,3>2x3 kutucukların oluşturulacağını belirtir. Bu nedenle, 8x9 matrisi 12 kutucuk, dört satır ve üç sütun halinde döşenir.parallel_for_eachyöntemi, dizin olarak birtiled_index<2,3>nesne (t_idx) kullanılarak çağrılır. Dizinin (t_idx) tür parametreleri, işlem etki alanının (descriptions.extent.tile< 2, 3>()) tür parametreleriyle eşleşmelidir.Her iş parçacığı yürütürken dizin, iş parçacığının
t_idxhangi kutucukta (tiled_index::tileözellik) olduğuna ve iş parçacığının kutucuk içindeki konumuna (tiled_index::localözellik) ilişkin bilgileri döndürür.
Kutucuk Senkronizasyonu—tile_static ve tile_barrier::wait
Önceki örnekte kutucuk düzeni ve dizinleri gösterilmektedir, ancak kendi içinde çok kullanışlı değildir. Algoritmanın ayrılmaz bir parçası olan ve tile_static değişkenlerinden yararlanan kutucuklar, döşeme işlemini yararlı hale getirir. Bir kutucuktaki tüm iş parçacıkları tile_static değişkenlerine erişime sahip olduğundan, tile_static değişkenlerinin erişimini eşitlemek için tile_barrier::wait çağrıları kullanılır. Bir kutucuktaki tüm iş parçacıklarının değişkenlere tile_static erişimi olsa da, kutucuktaki iş parçacıklarının yürütülmesi için garantili bir sıra yoktur. Aşağıdaki örnekte, her kutucuğun ortalama değerini hesaplamak için tile_static değişkenleri ve tile_barrier::wait yöntemi nasıl kullanılacağı gösterilmektedir. Örneği anlamanın anahtarları şunlardır:
rawData, 8x8 matrisinde depolanır.
Kutucuk boyutu 2x2'dir. Bu, 4x4 fayans ızgarası oluşturur ve ortalamalar
arraynesnesi kullanılarak 4x4 matrisinde depolanabilir. AMP kısıtlanmış işlevde referans olarak yakalayabileceğiniz yalnızca sınırlı sayıda tür vardır. Sınıfarraybunlardan biridir.Matris boyutu ve örnek boyutu,
#definedeyimleri kullanılarak tanımlanır, çünküarray,array_view,extentvetiled_indextür parametreleri sabit değerler olmalıdır. Bildirimleri de kullanabilirsinizconst int static. Ek bir avantaj olarak, 4x4 katmanlar üzerinde ortalama hesaplamak için örnek boyutunu değiştirmek oldukça kolaydır.tile_staticHer kutucuk için 2x2 kayan değer dizisi bildirilir. Bildirim her iş parçacığının kod yolu üzerindeyken, matristeki her karo için yalnızca bir dizi oluşturulur.Her kutucuktaki değerleri diziye kopyalamak için
tile_staticbir kod satırı vardır. Her iş parçacığı için değer diziye kopyalandıktan sonra,tile_barrier::waitçağrısı nedeniyle iş parçacığındaki yürütme durur.Bir kutucuktaki tüm iş parçacıkları bariyere ulaştığında ortalama hesaplanabilir. Kod her iş parçacığı için yürütülür çünkü yalnızca bir iş parçacığında ortalamayı hesaplamak için bir
ifdeyimi vardır. Ortalama, averages değişkeninde depolanır. Engel, tıpkı birfordöngüsü kullanır gibi, hesaplamaları kutucuklara göre kontrol eden yapıdır.Değişkendeki
averagesveriler birarraynesne olduğundan konağa geri kopyalanmalıdır. Bu örnekte vektör dönüştürme işleci kullanılır.Tam örnekte SAMPLESIZE değerini 4 olarak değiştirebilirsiniz ve kod başka bir değişiklik yapmadan doğru şekilde yürütülür.
#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();
}
Yarış Koşulları
Adlı tile_static bir total değişken oluşturmak ve her iş parçacığı için bu değişkeni artırmak cazip olabilir, örneğin:
// 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);
Bu yaklaşımdaki ilk sorun, değişkenlerin tile_static başlatıcıları olamayacağıdır. İkinci sorun, kutucuktaki tüm iş parçacıklarının gelişigüzel bir sırada değişkene erişmesi nedeniyle total atamasında bir yarış durumu olmasıdır. Bir algoritmayı, bir sonraki adımda gösterildiği gibi her bir engeldeki toplama yalnızca bir iş parçacığının erişmesine izin verecek şekilde programlayabilirsiniz. Ancak, bu çözüm genişletilebilir değildir.
// 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.
Bellek Çitleri
Eşitlenmesi gereken iki tür bellek erişimi vardır: genel bellek erişimi ve tile_static bellek erişimi. Nesne concurrency::array yalnızca genel bellek ayırır. Bir concurrency::array_view, genel belleğe, tile_static belleğe veya her ikisine de, oluşturulma biçimine bağlı olarak başvurabilir. Eşitlenmesi gereken iki tür bellek vardır:
genel bellek
tile_static
Bellek çiti, bellek erişimlerinin iş parçacığı kutucuğundaki diğer iş parçacıkları için kullanılabilir olmasını ve bellek erişimlerinin program sırasına göre yürütülmesini sağlar. Bunu sağlamak için, derleyiciler ve işlemciler okumaları ve yazmaları çit boyunca yeniden sıralamaz. C++ AMP'de, şu yöntemlerden birine yapılan bir çağrıyla bir bellek çiti oluşturulur:
tile_barrier::wait Yöntemi: Hem genel
tile_statichem de bellek çevresinde bir çit oluşturur.tile_barrier::wait_with_all_memory_fence Yöntemi: Hem global hem de
tile_staticbellek etrafında bir bariyer oluşturur.tile_barrier::wait_with_global_memory_fence Yöntemi: Yalnızca genel belleğin çevresinde bir çit oluşturur.
tile_barrier::wait_with_tile_static_memory_fence Yöntemi: Yalnızca
tile_staticbelleğin etrafında bir sınır çiti oluşturur.
Size gereken belirli çitleri çağırmak uygulamanızın performansını artırabilir. Engel türü, derleyicinin ve donanımın deyimlerini yeniden sıralama biçimini etkiler. Örneğin, genel bellek çiti kullanırsanız, bu yalnızca genel bellek erişimleri için geçerlidir ve bu nedenle derleyici ve donanım, çitin iki tarafındaki değişkenlere tile_static yönelik okuma ve yazmaları yeniden sıralayabilir.
Sonraki örnekte, bariyer bir tileValues değişkenine yapılan yazmaları senkronize eder. Bu örnekte, tile_barrier::wait_with_tile_static_memory_fence yerine tile_barrier::wait olarak adlandırılır.
// 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);
}
});
Ayrıca bakınız
C++ AMP (C++ Hızlandırılmış Yüksek Paralellik)
tile_static Anahtar Sözcüğü