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.
Bu adım adım kılavuzda, matris çarpmasının yürütülmesini hızlandırmak için C++ AMP'nin nasıl kullanılacağı gösterilmektedir. Biri döşemesiz, diğeri de döşemeli iki algoritma sunulur.
Önkoşullar
Başlamadan önce:
- C++ AMP'ye Genel Bakış'a bakın.
- Kutucukları Kullanarak Okuma.
- En az Windows 7 veya Windows Server 2008 R2 çalıştırdığınızdan emin olun.
Not
C++ AMP üst bilgileri Visual Studio 2022 sürüm 17.0'dan itibaren kullanım dışı bırakılmıştır.
Tüm AMP üst bilgileri dahil olmak derleme hataları oluşturur. Uyarıları susturmak için AMP üst bilgilerini eklemeden önce tanımlayın _SILENCE_AMP_DEPRECATION_WARNINGS
.
Proje oluşturmak için
Yeni proje oluşturma yönergeleri, hangi Visual Studio sürümünü yüklediğinize bağlı olarak değişir. Tercih ettiğiniz Visual Studio sürümünün belgelerini görmek için Sürüm seçici denetimini kullanın. Bu sayfadaki içindekiler tablosunun en üstünde bulunur.
Visual Studio'da proje oluşturmak için
Menü çubuğunda Dosya Yeni Proje'yi seçerek >Yeni>Proje Oluştur iletişim kutusunu açın.
İletişim kutusunun üst kısmında Dil'i C++ olarak, Platform'ı Windows olarak ve Proje türü'nü Konsol olarak ayarlayın.
Filtrelenen proje türleri listesinden Projeyi Boşalt'ı ve ardından İleri'yi seçin. Sonraki sayfada, Proje için bir ad belirtmek için Ad kutusuna MatrixMultiply yazın ve isterseniz proje konumunu belirtin.
İstemci projesini oluşturmak için Oluştur düğmesini seçin.
Çözüm Gezgini'da Kaynak Dosyalar kısayol menüsünü açın ve ardından Yeni Öğe.
Yeni Öğe Ekle iletişim kutusunda C++ Dosya (.cpp) öğesini seçin, Ad kutusuna MatrixMultiply.cppgirin ve Ekle düğmesini seçin.
Visual Studio 2017 veya 2015'te proje oluşturmak için
Visual Studio'daki menü çubuğunda Dosya>> seçin.
Şablonlar bölmesinde yüklü bölümünde Visual C++ öğesini seçin.
Projeyi Boşalt'ı seçin, Ad kutusuna MatrixMultiply yazın ve tamam düğmesini seçin.
İleri düğmesini seçin.
Çözüm Gezgini'da Kaynak Dosyalar kısayol menüsünü açın ve ardından Yeni Öğe.
Yeni Öğe Ekle iletişim kutusunda C++ Dosya (.cpp) öğesini seçin, Ad kutusuna MatrixMultiply.cppgirin ve Ekle düğmesini seçin.
Döşeme olmadan çarpma
Bu bölümde, A ve B olmak üzere aşağıdaki gibi tanımlanan iki matrisin çarpımını göz önünde bulundurun:
A, 3'e 2 matris, B ise 2'ye 3 matristir. A'nın B ile çarpılması aşağıdaki 3'e 3 matristir. Ürün, A'nın satırları B öğesinin sütunlarıyla öğeye göre çarpılarak hesaplanır.
C++ AMP kullanmadan çarpmak için
MatrixMultiply.cpp açın ve mevcut kodu değiştirmek için aşağıdaki kodu kullanın.
#include <iostream> void MultiplyWithOutAMP() { int aMatrix[3][2] = {{1, 4}, {2, 5}, {3, 6}}; int bMatrix[2][3] = {{7, 8, 9}, {10, 11, 12}}; int product[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { // Multiply the row of A by the column of B to get the row, column of product. for (int inner = 0; inner < 2; inner++) { product[row][col] += aMatrix[row][inner] * bMatrix[inner][col]; } std::cout << product[row][col] << " "; } std::cout << "\n"; } } int main() { MultiplyWithOutAMP(); getchar(); }
Algoritma, matris çarpma tanımının basit bir uygulamasıdır. Hesaplama süresini azaltmak için paralel veya iş parçacıklı algoritmalar kullanmaz.
Menü çubuğunda Dosya
Hata ayıklamayı başlatmak ve çıkışın doğru olduğunu doğrulamak için F5 klavye kısayolunu seçin.
Uygulamadan çıkmak için Enter'ı seçin.
C++ AMP kullanarak çarpmak için
MatrixMultiply.cpp'da yönteminden
main
önce aşağıdaki kodu ekleyin.void MultiplyWithAMP() { int aMatrix[] = { 1, 4, 2, 5, 3, 6 }; int bMatrix[] = { 7, 8, 9, 10, 11, 12 }; int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; array_view<int, 2> a(3, 2, aMatrix); array_view<int, 2> b(2, 3, bMatrix); array_view<int, 2> product(3, 3, productMatrix); parallel_for_each(product.extent, [=] (index<2> idx) restrict(amp) { int row = idx[0]; int col = idx[1]; for (int inner = 0; inner <2; inner++) { product[idx] += a(row, inner)* b(inner, col); } }); product.synchronize(); for (int row = 0; row <3; row++) { for (int col = 0; col <3; col++) { //std::cout << productMatrix[row*3 + col] << " "; std::cout << product(row, col) << " "; } std::cout << "\n"; } }
AMP kodu, AMP olmayan koda benzer. çağrısı
parallel_for_each
, içindekiproduct.extent
her öğe için bir iş parçacığı başlatır ve satır ve sütun döngülerinin yerini alırfor
. Satır ve sütundaki hücrenin değeri içindeidx
kullanılabilir. bir nesneninarray_view
öğelerine, işlecini ve dizin değişkenini ya da[]
işlecini()
ve satır ve sütun değişkenlerini kullanarak erişebilirsiniz. Örnekte her iki yöntem de gösterilmektedir. yöntemi değişkenininarray_view::synchronize
değerleriniproduct
değişkenineproductMatrix
geri kopyalar.MatrixMultiply.cpp en üstüne aşağıdaki
include
veusing
deyimlerini ekleyin.#include <amp.h> using namespace concurrency;
main
yöntemini çağıracak şekildeMultiplyWithAMP
değiştirin.int main() { MultiplyWithOutAMP(); MultiplyWithAMP(); getchar(); }
Hata ayıklamayı başlatmak ve çıkışın doğru olduğunu doğrulamak için Ctrl+F5 klavye kısayolunu basın.
Uygulamadan çıkmak için Ara Çubuğu'na basın.
Döşeme ile çarpma
Döşeme, verileri kutucuk olarak bilinen eşit boyutlu alt kümelere bölümlemenizi sağlayan bir tekniktir. Döşemeyi kullandığınızda üç şey değişir.
Değişkenler oluşturabilirsiniz
tile_static
. Alan içindekitile_static
verilere erişim, genel alanda verilere erişimden çok daha hızlı olabilir. Her kutucuk için birtile_static
değişken örneği oluşturulur ve kutucuktaki tüm iş parçacıkları değişkene erişebilir. Döşemenin birincil avantajı, erişimden kaynaklanantile_static
performans kazancıdır.Belirtilen kod satırında tek bir kutucuktaki tüm iş parçacıklarını durdurmak için tile_barrier::wait yöntemini çağırabilirsiniz. İş parçacıklarının çalıştırılacağı sırayı garanti edemezsiniz, yalnızca bir kutucuktaki tüm iş parçacıklarının yürütmeye
tile_barrier::wait
devam etmeden önce çağrısında durdurulacağını garanti edemezsiniz.İş parçacığının dizinine nesnenin tamamına
array_view
ve kutucuğa göre dizine erişiminiz vardır. Yerel dizini kullanarak kodunuzun daha kolay okunmasını ve hata ayıklamasını sağlayabilirsiniz.
Matris çarpmasında döşemeden yararlanmak için algoritmanın matrisi kutucuklara ayırması ve daha hızlı erişim için kutucuk verilerini değişkenlere tile_static
kopyalaması gerekir. Bu örnekte matris, eşit boyutta alt matrisler halinde bölümlenmiştir. Ürün, alt matrisler çarpılarak bulunur. Bu örnekteki iki matris ve bunların ürünü şunlardır:
Matrisler dört adet 2x2 matrise ayrılır ve bunlar aşağıdaki gibi tanımlanır:
A ve B'nin ürünü artık aşağıdaki gibi yazılabilir ve hesaplanabilir:
aracılığıyla a
matrisler h
2x2 matris olduğundan, tüm ürünler ve bunların toplamları da 2x2 matristir. Ayrıca A ve B'nin çarpımının beklendiği gibi 4x4 matris olduğunu da izler. Algoritmayı hızla denetlemek için, ürünün ilk satırındaki ilk sütundaki öğenin değerini hesaplayın. Örnekte bu, öğesinin ilk satırındaki ve ilk sütunundaki ae + bg
değeri olacaktır. Her terim için yalnızca ilk ve sütununu ae
bg
hesaplamanız gerekir. için ae
bu değer şeklindedir (1 * 1) + (2 * 5) = 11
. için bg
değeri şeklindedir (3 * 1) + (4 * 5) = 23
. Son değer, 11 + 23 = 34
doğru olan değeridir.
Bu algoritmayı uygulamak için kod:
tiled_extent
Çağrısındaextent
nesne yerine nesneparallel_for_each
kullanır.tiled_index
Çağrısındaindex
nesne yerine nesneparallel_for_each
kullanır.Alt değerleri tutmak için değişkenler oluşturur
tile_static
.tile_barrier::wait
Alt matrislerin ürünlerinin hesaplanması için iş parçacıklarını durdurmak için yöntemini kullanır.
AMP ve döşeme kullanarak çarpmak için
MatrixMultiply.cpp'da yönteminden
main
önce aşağıdaki kodu ekleyin.void MultiplyWithTiling() { // The tile size is 2. static const int TS = 2; // The raw data. int aMatrix[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; int bMatrix[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }; int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Create the array_view objects. array_view<int, 2> a(4, 4, aMatrix); array_view<int, 2> b(4, 4, bMatrix); array_view<int, 2> product(4, 4, productMatrix); // Call parallel_for_each by using 2x2 tiles. parallel_for_each(product.extent.tile<TS, TS>(), [=] (tiled_index<TS, TS> t_idx) restrict(amp) { // Get the location of the thread relative to the tile (row, col) // and the entire array_view (rowGlobal, colGlobal). int row = t_idx.local[0]; int col = t_idx.local[1]; int rowGlobal = t_idx.global[0]; int colGlobal = t_idx.global[1]; int sum = 0; // Given a 4x4 matrix and a 2x2 tile size, this loop executes twice for each thread. // For the first tile and the first loop, it copies a into locA and e into locB. // For the first tile and the second loop, it copies b into locA and g into locB. for (int i = 0; i < 4; i += TS) { tile_static int locA[TS][TS]; tile_static int locB[TS][TS]; locA[row][col] = a(rowGlobal, col + i); locB[row][col] = b(row + i, colGlobal); // The threads in the tile all wait here until locA and locB are filled. t_idx.barrier.wait(); // Return the product for the thread. The sum is retained across // both iterations of the loop, in effect adding the two products // together, for example, a*e. for (int k = 0; k < TS; k++) { sum += locA[row][k] * locB[k][col]; } // All threads must wait until the sums are calculated. If any threads // moved ahead, the values in locA and locB would change. t_idx.barrier.wait(); // Now go on to the next iteration of the loop. } // After both iterations of the loop, copy the sum to the product variable by using the global location. product[t_idx.global] = sum; }); // Copy the contents of product back to the productMatrix variable. product.synchronize(); for (int row = 0; row <4; row++) { for (int col = 0; col <4; col++) { // The results are available from both the product and productMatrix variables. //std::cout << productMatrix[row*3 + col] << " "; std::cout << product(row, col) << " "; } std::cout << "\n"; } }
Bu örnek, döşeme olmadan örnekten önemli ölçüde farklıdır. Kod şu kavramsal adımları kullanır:
kutucuğunun [0,0]
a
öğelerini içinelocA
kopyalayın. kutucuğunun [0,0]b
öğelerini içinelocB
kopyalayın. ve değilproduct
a
kutucuklu olduğunab
dikkat edin. Bu nedenle, vea, b
öğesine erişmekproduct
için genel dizinler kullanırsınız. çağrısıtile_barrier::wait
çok önemlidir. Kutucuktaki tüm iş parçacıklarını hem hem delocA
locB
doldurulana kadar durdurur.Çarpın
locA
velocB
sonuçları içineproduct
yerleştirin.kutucuğunun [0,1]
a
öğelerini içinelocA
kopyalayın. [1,0] kutucuğununb
öğelerini içinelocB
kopyalayın.Çarpın
locA
velocB
bunları zaten içindeproduct
olan sonuçlara ekleyin.Kutucuğun [0,0] çarpması tamamlandı.
Diğer dört kutucuk için yineleyin. Özel olarak kutucuklar için dizin oluşturma yoktur ve iş parçacıkları herhangi bir sırada yürütülebilir. Her iş parçacığı yürütülürken,
tile_static
değişkenler her kutucuk için uygun şekilde oluşturulur ve program akışını denetleme çağrısıtile_barrier::wait
yapılır.Algoritmayı yakından incelediğinizde, her alt matrisin bir
tile_static
belleğe iki kez yüklendiğine dikkat edin. Bu veri aktarımı zaman alır. Ancak verilertile_static
belleğe girdikten sonra verilere erişim çok daha hızlı olur. Ürünlerin hesaplanması alt matrislerdeki değerlere tekrar tekrar erişim gerektirdiğinden, genel bir performans artışı vardır. Her algoritma için, en uygun algoritmayı ve kutucuk boyutunu bulmak için deneme gerekir.
AMP olmayan ve kutucuk olmayan örneklerde, A ve B öğelerinin her birine genel bellekten dört kez erişilir ve ürün hesaplanır. Kutucuk örneğinde, her öğeye genel bellekten iki kez ve bellekten
tile_static
dört kez erişilir. Bu önemli bir performans kazancı değildir. Ancak, A ve B 1024x1024 matrisleri ve kutucuk boyutu 16 ise önemli bir performans artışı olacaktır. Bu durumda, her öğe yalnızca 16 kez belleğe kopyalanırtile_static
ve bellektentile_static
1024 kez erişilir.Gösterildiği gibi yöntemini çağırmak
MultiplyWithTiling
için main yöntemini değiştirin.int main() { MultiplyWithOutAMP(); MultiplyWithAMP(); MultiplyWithTiling(); getchar(); }
Hata ayıklamayı başlatmak ve çıkışın doğru olduğunu doğrulamak için Ctrl+F5 klavye kısayolunu basın.
Uygulamadan çıkmak için Ara çubuğuna basın.
Ayrıca bkz.
C++ AMP (C++ Hızlandırılmış Yüksek Paralellik)
İzlenecek yol: C++ AMP Uygulamasında Hata Ayıklama