1. 實驗內容與方法
1) 對矩陣進行分塊,之後按塊進行乘法,使用的是cannon算法。Cannon算法是一種存儲有效的算法。爲了使兩矩陣下標滿足相乘的要求,它和並行分塊乘法不同,不是僅僅讓B矩陣的各列塊循環移動,而是有目的地讓A的各行塊以及B的各列塊皆施行循環移位,從而實現對C的子塊的計算。將矩陣A和B分成p個方塊Aij和Bij,,每塊大小爲,並將它們分配給個處理器。開始時處理器Pij存放塊Aij和Bij,並負責計算塊Cij,然後算法開始執行:
⑴將塊Aij向左循環移動i步;將塊Bij向上循環移動j步;
⑵Pij執行乘加運算後將塊Aij向左循環移動1步,塊Bij向上循環移動1步;
⑶重複第⑵步,總共執行次乘加運算和次塊Aij和Bij的循環單步移位。
2) 初始化數組。使用vector定義矩陣A、B和C,設置矩陣大小均爲6000 * 6000。使用srand((unsigned) time(NULL))設置使用系統定時器的值作爲隨機種子,隨機初始化矩陣A和矩陣B的值。
3) 程序計時。使用MPI自帶的MPI_Wtime()函數,MPI_Wtime()記錄的是牆上時間,在CPU時間的基礎上包括了空閒等待時間。在各進程計算過程分塊矩陣C之前和之後使用MPI_Wtime()函數計時,不計算最開始的初始化過程MPI發送的時間和最後接收數據彙總成矩陣C的時間。
2. 運行時間
矩陣A、B和C的大小均爲6000 * 6000,且。經過代碼運行計時,得到以下的實驗結果表格。
處理器個數 |
用時/秒 |
串行 |
1734.048241 |
4核 |
321.548999 |
9核 |
194.883003 |
16核 |
111.360064 |
25核 |
61.248356 |
36核(3 * 12) |
35.795692 |
3. 加速比
處理器個數 |
加速比 |
串行 |
1 |
4核 |
5.39279627 |
9核 |
8.89789368 |
16核 |
15.5715449 |
25核 |
28.3117516 |
36核(3 * 12) |
48.4429311 |
4. 實驗分析
從實驗結果和加速比可以看出:
1 隨着核數的增加,矩陣乘法所需要的時間在相應縮短;
2 矩陣乘法的加速比與運行的核心數近似成正比;
3 核心數越多,每個核分到的分塊矩陣就越小,每個覈計算速度就越快,加速比比核心數更高。
5. 源代碼
#include "iostream"
#include "cstdlib"
#include "cstring"
#include "mpi.h"
#include "ctime"
#include "cmath"
#include "algorithm"
#include "vector"
using namespace std;
#define N 6000 //n是矩陣大小
// 全局變量聲明
int block, block_size, p, sp;//, block是塊的大小,block_size是塊元素個數,等於block*block,p是處理器個數,sp是sqrt(p)
vector<vector<double>> A(N, vector<double>(N, 0)), B(N, vector<double>(N, 0)), C(N, vector<double>(N, 0));
double *a, *b, *c, *temp_a, *temp_b;
int my_rank, my_row, my_col;// my_rank是處理器的id,(my_row, my_col)是處理器邏輯陣列座標
MPI_Status status;
double start_time, end_time;
int get_index(int row, int col, int sp);//處理器邏輯陣列座標到rank的轉換
void init_Matrix();//隨機生成矩陣A和B
void scatter_A_B();//id爲0的處理器向其他處理器發送矩陣A、B的分塊
void init_alignment();//矩陣A和B的初始化,即A每行不同程度的左移,B每行不同程度的上移
void shift();//矩陣A每行左移一位,矩陣B每行上移一位,同時計算分塊的中間結果C
void collect_C();//id爲0的處理器從其餘處理器收集分塊矩陣C
void serial_Multiplication();//矩陣串行的計算
int main() {
MPI_Init(NULL, NULL);
MPI_Comm_size(MPI_COMM_WORLD, &p);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
sp = sqrt(p);
if (sp * sp != p) {
if (my_rank == 0)
cout << "not a quadratic number" << endl;
MPI_Finalize();
exit(1);
}
block = N / sp;
block_size = block * block;
my_col = my_rank % sp;
my_row = my_rank / sp;
a = (double *) malloc(block_size * sizeof(double));
b = (double *) malloc(block_size * sizeof(double));
c = (double *) malloc(block_size * sizeof(double));;
for (int i = 0; i < block_size; ++i) {
c[i] = 0;
}
temp_a = (double *) malloc(block_size * sizeof(double));
temp_b = (double *) malloc(block_size * sizeof(double));
if (my_rank == 0) {
init_Matrix();
scatter_A_B();
} else {
MPI_Recv(a, block_size, MPI_FLOAT, 0, 1, MPI_COMM_WORLD, &status);
MPI_Recv(b, block_size, MPI_FLOAT, 0, 2, MPI_COMM_WORLD, &status);
}
start_time = MPI_Wtime();
init_alignment();
shift();
end_time = MPI_Wtime();
if (my_rank == 0) {
cout << "matrix size : " << N << " * " << N << endl;
printf("time = %lf\n", end_time - start_time);
}
if (my_rank == 0) {
collect_C();
} else {
MPI_Send(c, block_size, MPI_FLOAT, 0, 1, MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
/*
處理器邏輯陣列座標到rank的轉換
輸入:座標(row, col),sqrt(p)
輸出:rank
*/
int get_index(int row, int col, int sp) {
return ((row + sp) % sp) * sp + (col + sp) % sp;
}
/*
隨機生成矩陣A和B
*/
void init_Matrix() {
srand((unsigned int) time(NULL)); //設置隨機數種子
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
A[i][j] = rand();
B[i][j] = rand();
}
}
}
/*
id爲0的處理器向其他處理器發送矩陣A、B的分塊
*/
void scatter_A_B() {
int p_i_min, p_j_min, p_i_max, p_j_max;
for (int k = 0; k < p; k++) {
p_j_min = (k % sp) * block;
p_j_max = (k % sp + 1) * block - 1;
p_i_min = k / sp * block;
p_i_max = (k / sp + 1) * block - 1;
// id爲0的處理器將矩陣A和B中的數據轉換成一位數組傳到temp中
for (int i = p_i_min; i <= p_i_max; ++i) {
for (int j = p_j_min; j <= p_j_max; j++) {
temp_a[(i - p_i_min) * block + j - p_j_min] = A[i][j];
temp_b[(i - p_i_min) * block + j - p_j_min] = B[i][j];
}
}
if (k == 0) { // id爲0的處理器直接拷貝過去,其他處理器則發送過去
memcpy(a, temp_a, block_size * sizeof(double));
memcpy(b, temp_b, block_size * sizeof(double));
} else {
MPI_Send(temp_a, block_size, MPI_FLOAT, k, 1, MPI_COMM_WORLD);
MPI_Send(temp_b, block_size, MPI_FLOAT, k, 2, MPI_COMM_WORLD);
}
}
}
/*
將A中座標爲(i, j)的分塊循環左移i步
將B中座標爲(i, j)的分塊循環上移j步
*/
void init_alignment() {
MPI_Sendrecv(a, block_size, MPI_FLOAT, get_index(my_row, my_col - my_row, sp), 1, temp_a, block_size, MPI_FLOAT,
get_index(my_row, my_col + my_row, sp), 1, MPI_COMM_WORLD, &status);
memcpy(a, temp_a, block_size * sizeof(double));
MPI_Sendrecv(b, block_size, MPI_FLOAT, get_index(my_row - my_col, my_col, sp), 1, temp_b, block_size, MPI_FLOAT,
get_index(my_row + my_col, my_col, sp), 1, MPI_COMM_WORLD, &status);
memcpy(b, temp_b, block_size * sizeof(double));
}
/*
矩陣A每行左移一位,矩陣B每行上移一位,同時計算分塊的中間結果C
*/
void shift() {
double total = 0;
for (int l = 0; l < sp; l++) {
for (int i = 0; i < block; i++) {
for (int j = 0; j < block; j++) {
total = c[i * block + j];
for (int k = 0; k < block; k++) {
total += a[i * block + k] * b[k * block + j];
}
c[i * block + j] = total;
}
}
// 將A全部循環左移1位
MPI_Sendrecv(a, block_size, MPI_FLOAT, get_index(my_row, my_col - 1, sp), 1, temp_a, block_size, MPI_FLOAT,
get_index(my_row, my_col + 1, sp), 1, MPI_COMM_WORLD, &status);
memcpy(a, temp_a, block_size * sizeof(double));
// 將B全部循環上移1位
MPI_Sendrecv(b, block_size, MPI_FLOAT, get_index(my_row - 1, my_col, sp), 1, temp_b, block_size, MPI_FLOAT,
get_index(my_row + 1, my_col, sp), 1, MPI_COMM_WORLD, &status);
memcpy(b, temp_b, block_size * sizeof(double));
}
}
/*
id爲0的處理器從其餘處理器收集分塊矩陣C
*/
void collect_C() {
int p_i_min, p_j_min, p_i_max, p_j_max;
// 將id爲0的處理器的分塊矩陣c結果賦給C對應位置
for (int i = 0; i < block; i++) {
for (int j = 0; j < block; j++) {
C[i][j] = c[i * block + j];
}
}
for (int k = 1; k < p; k++) {
MPI_Recv(c, block_size, MPI_FLOAT, k, 1, MPI_COMM_WORLD, &status);
p_j_min = (k % sp) * block;
p_j_max = (k % sp + 1) * block - 1;
p_i_min = k / sp * block;
p_i_max = (k / sp + 1) * block - 1;
for (int i = p_i_min; i <= p_i_max; i++) {
for (int j = p_j_min; j <= p_j_max; ++j) {
C[i][j] = c[(i - p_i_min) * block + j - p_j_min];
}
}
}
}
void serial_Multiplication();//矩陣串行的計算 使用1個線程即串行的計算