walkthrough-matrix-multiplication
不加圖文的乘法
在本節中,請考慮兩個矩陣 A 和 B 的乘法,其定義如下:


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

若要在不使用 C++ AMP 的情況下相乘
開啟 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
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();
}演算法是矩陣乘法定義的直接實作。 它不會使用任何平行或執行緒演算法來減少計算時間。
在功能表列上,選擇 [檔案>][全部儲存]。
選擇 F5 鍵盤快速鍵以開始偵錯,並確認輸出正確無誤。
選擇 Enter 以結束應用程式。
使用 C++ AMP 乘以
在 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
30void 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變數。在 MatrixMultiply.cpp 頂端新增下列
include和using語句。C++複製
1
2
using namespace concurrency;main修改 方法以呼叫MultiplyWithAMP方法。C++複製
1
2
3
4
5int main() {
MultiplyWithOutAMP();
MultiplyWithAMP();
getchar();
}按CtrlF5+ 鍵盤快速鍵以開始偵錯,並確認輸出正確無誤。
按 空格鍵 以結束應用程式。
使用拼貼的乘法
圖格是一種技術,可讓您將資料分割成相等大小的子集,也就是所謂的磚。 當您使用並排時,有三件事會變更。
- 您可以建立
tile_static變數。 存取空間中的資料tile_static可能會比存取全域空間中的資料快許多倍。 系統會為每個磚建立變數的tile_static實例,而且磚中的所有線程都可以存取變數。 磚的主要優點是由於存取而獲得tile_static效能。 - 您可以呼叫 tile_barrier::wait 方法,在指定的程式程式碼停止一個磚中的所有線程。 您無法保證執行緒執行的順序,只有一個圖格中的所有線程都會在呼叫
tile_barrier::wait時停止,再繼續執行。 - 您可以存取相對於整個
array_view物件的執行緒索引,以及相對於磚的索引。 藉由使用本機索引,您可以更輕鬆地讀取和偵錯程式碼。
若要利用矩陣乘法中的並排,演算法必須將矩陣分割成磚,然後將磚資料 tile_static 複製到變數中,以加快存取速度。 在此範例中,矩陣會分割成大小相等的子矩陣。 乘以子系來找到乘積。 此範例中的兩個矩陣及其產品為:



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


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

因為矩陣 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 和並排
在 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
66void 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";
}
}這個範例與範例明顯不同,但不含並排。 程式碼會使用這些概念步驟:
- 將 tile[0,0] 的專案
a``locA複製到 。 將 tile[0,0] 的專案b``locB複製到 。 請注意,product已並排顯示,而非a和b。 因此,您會使用全域索引來存取a, b、 和product。 的呼叫tile_barrier::wait是不可或缺的。 它會停止磚中的所有線程,直到locA和locB都填滿為止。 - 將 和
locB相乘locA,並將結果product放在 中。 - 將 的磚[0,1]
a``locA元素複製到 。 將 的磚 [1,0]b``locB元素複製到 。 - 將 和
locB相乘locA,並將其新增至 中已存在product的結果。 - tile[0,0] 的乘法已完成。
- 針對其他四個磚重複。 磚沒有特別編制索引,而且執行緒可以依任何循序執行。 當每個執行緒執行時,會
tile_static適當地為每個磚建立變數,以及控制程式流程的呼叫tile_barrier::wait。 - 當您仔細檢查演算法時,請注意,每個子資料庫都會載入記憶體
tile_static兩次。 該資料傳輸需要一段時間。 不過,一旦資料位於記憶體中tile_static,資料的存取速度會更快。 由於計算產品需要重複存取子目錄中的值,因此整體效能提升。 針對每個演算法,必須進行實驗,才能尋找最佳的演算法和磚大小。
在非 AMP 和非磚範例中,A 和 B 的每個元素都會從全域記憶體存取四次,以計算產品。 在圖格範例中,每個元素都會從全域記憶體存取兩次,並從記憶體存取四次
tile_static。 這不是顯著的效能提升。 不過,如果 A 和 B 是 1024x1024 矩陣,而磚大小為 16,則會大幅提升效能。 在此情況下,每個元素只會複製到tile_static記憶體 16 次,並從記憶體 1024 次存取tile_static。- 將 tile[0,0] 的專案
修改 main 方法以呼叫
MultiplyWithTiling方法,如下所示。C++複製
1
2
3
4
5
6int main() {
MultiplyWithOutAMP();
MultiplyWithAMP();
MultiplyWithTiling();
getchar();
}