walkthrough-matrix-multiplication

不加圖文的乘法

在本節中,請考慮兩個矩陣 A 和 B 的乘法,其定義如下:

Diagram showing 3 by 2 matrix A.

Diagram showing 2 by 3 matrix B.

是 3 by-2 矩陣,B 是 2-by-3 矩陣。 將 A 乘以 B 的乘積是下列 3-by-3 矩陣。 乘以 B 元素的資料行乘以 A 的資料列來計算乘積。

Diagram showing the result 3 by 3 product matrix.

若要在不使用 C++ AMP 的情況下相乘

  1. 開啟 MatrixMultiply.cpp,並使用下列程式碼來取代現有的程式碼。

    C++複製

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #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();
    }

    演算法是矩陣乘法定義的直接實作。 它不會使用任何平行或執行緒演算法來減少計算時間。

  2. 在功能表列上,選擇 [檔案>][全部儲存]。

  3. 選擇 F5 鍵盤快速鍵以開始偵錯,並確認輸出正確無誤。

  4. 選擇 Enter 以結束應用程式。

使用 C++ AMP 乘以

  1. 在 MatrixMultiply.cpp 中,于 方法之前 main 新增下列程式碼。

    C++複製

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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 程式碼類似于非 AMP 程式碼。 針對 中的每個 product.extent 專案啟動一個執行緒的呼叫 parallel_for_each ,並取代 for 資料列和資料行的迴圈。 資料列和資料行的資料格值可在 中使用 idx 。 您可以使用 運算子和索引變數,或是 () 運算子和資料列和資料行變數來存取 物件的 [] 元素 array_view 。 此範例示範這兩種方法。 方法 array_view::synchronize 會將變數的值 product 複製回 productMatrix 變數。

  2. 在 MatrixMultiply.cpp 頂端新增下列 include 和 using 語句。

    C++複製

    1
    2
    #include <amp.h>
    using namespace concurrency;
  3. main修改 方法以呼叫 MultiplyWithAMP 方法。

    C++複製

    1
    2
    3
    4
    5
    int main() {
    MultiplyWithOutAMP();
    MultiplyWithAMP();
    getchar();
    }
  4. 按CtrlF5+ 鍵盤快速鍵以開始偵錯,並確認輸出正確無誤。

  5. 按 空格鍵 以結束應用程式。

使用拼貼的乘法

圖格是一種技術,可讓您將資料分割成相等大小的子集,也就是所謂的磚。 當您使用並排時,有三件事會變更。

  • 您可以建立 tile_static 變數。 存取空間中的資料 tile_static 可能會比存取全域空間中的資料快許多倍。 系統會為每個磚建立變數的 tile_static 實例,而且磚中的所有線程都可以存取變數。 磚的主要優點是由於存取而獲得 tile_static 效能。
  • 您可以呼叫 tile_barrier::wait 方法,在指定的程式程式碼停止一個磚中的所有線程。 您無法保證執行緒執行的順序,只有一個圖格中的所有線程都會在呼叫 tile_barrier::wait 時停止,再繼續執行。
  • 您可以存取相對於整個 array_view 物件的執行緒索引,以及相對於磚的索引。 藉由使用本機索引,您可以更輕鬆地讀取和偵錯程式碼。

若要利用矩陣乘法中的並排,演算法必須將矩陣分割成磚,然後將磚資料 tile_static 複製到變數中,以加快存取速度。 在此範例中,矩陣會分割成大小相等的子矩陣。 乘以子系來找到乘積。 此範例中的兩個矩陣及其產品為:

Diagram showing 4 by 4 matrix A.

Diagram showing 4 by 4 matrix B.

Diagram showing result 4 by 4 product matrix.

矩陣會分割成四個 2x2 矩陣,其定義如下:

Diagram showing 4 by 4 matrix A partitioned into 2 by 2 sub matrices.

Diagram showing 4 by 4 matrix B partitioned into 2 by 2 sub matrices.

A 和 B 的乘積現在可以撰寫並計算如下:

Diagram showing 4 by 4 matrix A B partitioned into 2 by 2 sub matrices.

因為矩陣 a``h 是 2x2 矩陣,所以所有的產品和總和也是 2x2 矩陣。 它也會跟著 A 和 B 的乘積是 4x4 矩陣,如預期般。 若要快速檢查演算法,請計算產品中第一列第一欄的專案值。 在此範例中,這會是 中第一列和第一欄 ae + bg 的專案值。 您只需要計算每個字詞的第一個資料行、第一個資料列 ae 和 bg 。 的 ae``(1 * 1) + (2 * 5) = 11 值為 。 bg 的值為 (3 * 1) + (4 * 5) = 23。 最後一個 11 + 23 = 34 值為 ,正確。

若要實作此演算法,程式碼:

  • tiled_extent使用 物件, extent 而不是呼叫中的 parallel_for_each 物件。
  • tiled_index使用 物件, index 而不是呼叫中的 parallel_for_each 物件。
  • tile_static建立變數來保存子目錄。
  • tile_barrier::wait使用 方法來停止執行緒,以計運算元矩陣的產品。

若要乘以使用 AMP 和並排

  1. 在 MatrixMultiply.cpp 中,于 方法之前 main 新增下列程式碼。

    C++複製

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    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";
    }
    }

    這個範例與範例明顯不同,但不含並排。 程式碼會使用這些概念步驟:

    1. 將 tile[0,0] 的專案 a``locA 複製到 。 將 tile[0,0] 的專案 b``locB 複製到 。 請注意, product 已並排顯示,而非 a 和 b 。 因此,您會使用全域索引來存取 a, b 、 和 product 。 的呼叫 tile_barrier::wait 是不可或缺的。 它會停止磚中的所有線程,直到 locA 和 locB 都填滿為止。
    2. 將 和 locB 相乘 locA ,並將結果 product 放在 中。
    3. 將 的磚[0,1] a``locA 元素複製到 。 將 的磚 [1,0] b``locB 元素複製到 。
    4. 將 和 locB 相乘 locA ,並將其新增至 中已存在 product 的結果。
    5. tile[0,0] 的乘法已完成。
    6. 針對其他四個磚重複。 磚沒有特別編制索引,而且執行緒可以依任何循序執行。 當每個執行緒執行時,會 tile_static 適當地為每個磚建立變數,以及控制程式流程的呼叫 tile_barrier::wait 。
    7. 當您仔細檢查演算法時,請注意,每個子資料庫都會載入記憶體 tile_static 兩次。 該資料傳輸需要一段時間。 不過,一旦資料位於記憶體中 tile_static ,資料的存取速度會更快。 由於計算產品需要重複存取子目錄中的值,因此整體效能提升。 針對每個演算法,必須進行實驗,才能尋找最佳的演算法和磚大小。

    在非 AMP 和非磚範例中,A 和 B 的每個元素都會從全域記憶體存取四次,以計算產品。 在圖格範例中,每個元素都會從全域記憶體存取兩次,並從記憶體存取四次 tile_static 。 這不是顯著的效能提升。 不過,如果 A 和 B 是 1024x1024 矩陣,而磚大小為 16,則會大幅提升效能。 在此情況下,每個元素只會複製到 tile_static 記憶體 16 次,並從記憶體 1024 次存取 tile_static 。

  2. 修改 main 方法以呼叫 MultiplyWithTiling 方法,如下所示。

    C++複製

    1
    2
    3
    4
    5
    6
    int main() {
    MultiplyWithOutAMP();
    MultiplyWithAMP();
    MultiplyWithTiling();
    getchar();
    }