Aracılığıyla paylaş


İzlenecek yol: Matris Çarpım

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

  1. Menü çubuğunda Dosya Yeni Proje'yi seçerek >Yeni>Proje Oluştur iletişim kutusunu açın.

  2. İletişim kutusunun üst kısmında Dil'i C++ olarak, Platform'ı Windows olarak ve Proje türü'nü Konsol olarak ayarlayın.

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

    Konsol Uygulaması şablonunun seçili olduğu Yeni proje oluştur iletişim kutusunu gösteren ekran görüntüsü.

  4. İstemci projesini oluşturmak için Oluştur düğmesini seçin.

  5. Çözüm Gezgini'da Kaynak Dosyalar kısayol menüsünü açın ve ardından Yeni Öğe.

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

  1. Visual Studio'daki menü çubuğunda Dosya>> seçin.

  2. Şablonlar bölmesinde yüklü bölümünde Visual C++ öğesini seçin.

  3. Projeyi Boşalt'ı seçin, Ad kutusuna MatrixMultiply yazın ve tamam düğmesini seçin.

  4. İleri düğmesini seçin.

  5. Çözüm Gezgini'da Kaynak Dosyalar kısayol menüsünü açın ve ardından Yeni Öğe.

  6. 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:

3'e 2 matris A'nın gösterildiği diyagram.

2'ye 3 matris B'nin gösterildiği diyagram.

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.

3'e 3 ürün matrisinin sonucunu gösteren diyagram.

C++ AMP kullanmadan çarpmak için

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

  2. Menü çubuğunda Dosya

  3. Hata ayıklamayı başlatmak ve çıkışın doğru olduğunu doğrulamak için F5 klavye kısayolunu seçin.

  4. Uygulamadan çıkmak için Enter'ı seçin.

C++ AMP kullanarak çarpmak için

  1. 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çindeki product.extenther öğe için bir iş parçacığı başlatır ve satır ve sütun döngülerinin yerini alır for . Satır ve sütundaki hücrenin değeri içinde idxkullanılabilir. bir nesnenin array_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şkeninin array_view::synchronize değerlerini product değişkenine productMatrix geri kopyalar.

  2. MatrixMultiply.cpp en üstüne aşağıdaki include ve using deyimlerini ekleyin.

    #include <amp.h>
    using namespace concurrency;
    
  3. main yöntemini çağıracak şekilde MultiplyWithAMP değiştirin.

    int main() {
        MultiplyWithOutAMP();
        MultiplyWithAMP();
        getchar();
    }
    
  4. Hata ayıklamayı başlatmak ve çıkışın doğru olduğunu doğrulamak için Ctrl+F5 klavye kısayolunu basın.

  5. 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çindeki tile_static verilere erişim, genel alanda verilere erişimden çok daha hızlı olabilir. Her kutucuk için bir tile_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 kaynaklanan tile_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:

4'e 4 matris A'nın gösterildiği diyagram.

4'e 4 matris B'nin gösterildiği diyagram.

4'e 4 ürün matrisi sonucunu gösteren diyagram.

Matrisler dört adet 2x2 matrise ayrılır ve bunlar aşağıdaki gibi tanımlanır:

4'e 4 matris A'nın 2'ye 2 alt matrise bölümlendiğini gösteren diyagram.

4'e 4 matris B'nin 2'ye 2 alt matrise bölümlendiğini gösteren diyagram.

A ve B'nin ürünü artık aşağıdaki gibi yazılabilir ve hesaplanabilir:

4'e 4 matris A B'nin 2'ye 2 alt matrise bölümlendiğini gösteren diyagram.

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 + bgdeğeri olacaktır. Her terim için yalnızca ilk ve sütununu aebg 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 = 34doğru olan değeridir.

Bu algoritmayı uygulamak için kod:

  • tiled_extent Çağrısında extent nesne yerine nesne parallel_for_each kullanır.

  • tiled_index Çağrısında index nesne yerine nesne parallel_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

  1. 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:

    1. kutucuğunun [0,0] a öğelerini içine locAkopyalayın. kutucuğunun [0,0] b öğelerini içine locBkopyalayın. ve değil productakutucuklu olduğuna b dikkat edin. Bu nedenle, ve a, böğesine erişmek productiç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 de locAlocB doldurulana kadar durdurur.

    2. Çarpın locA ve locB sonuçları içine productyerleştirin.

    3. kutucuğunun [0,1] a öğelerini içine locAkopyalayın. [1,0] kutucuğunun b öğelerini içine locBkopyalayın.

    4. Çarpın locA ve locB bunları zaten içinde productolan sonuçlara ekleyin.

    5. Kutucuğun [0,0] çarpması tamamlandı.

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

    7. 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 veriler tile_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ır tile_static ve bellekten tile_static 1024 kez erişilir.

  2. Gösterildiği gibi yöntemini çağırmak MultiplyWithTiling için main yöntemini değiştirin.

    int main() {
        MultiplyWithOutAMP();
        MultiplyWithAMP();
        MultiplyWithTiling();
        getchar();
    }
    
  3. Hata ayıklamayı başlatmak ve çıkışın doğru olduğunu doğrulamak için Ctrl+F5 klavye kısayolunu basın.

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