矩陣乘法的MPI并行實驗報告
一、實驗要求:
(1) 分別用 1,2,4,8 個進程完成矩陣乘法(同一個程序):A * B = C,其中 A,B,C 均為 2048*2048 雙精度點方陣,0號進程負責(zé)初始化矩陣 A,B 并將結(jié)果存入 0 號進程。
(2) 繪制加速比曲線;
二、實驗環(huán)境:
- 操作系統(tǒng):Windows11
- 編程語言:C++(使用MPI接口)
- 編譯器:VC++
- 核心庫:MPI(MSMPI)
- 編程工具:Visual Studio 2022
- CPU:AMD Ryzen 7 6800H with Radeon Graphics 3.20 GHz
- 內(nèi)存:16GB
三、實驗內(nèi)容:
1. 實現(xiàn)思路
將可用于計算的進程數(shù)分解為a*b,然后將全體行劃分為a個部分,全體列劃分為b個部分,從而將整個矩陣劃分為相同的若干個塊。每個子進程負責(zé)計算最終結(jié)果的一塊,只需要接收A對應(yīng)范圍的行和B對應(yīng)范圍的列,而不需要把整個矩陣傳過去。主進程(0號進程)負責(zé)分發(fā)和匯總結(jié)果。
注意:
(1) 為了保證平均劃分,矩陣需要擴展,即擴展至負責(zé)計算的進程數(shù)的倍數(shù),擴展部分?jǐn)?shù)據(jù)置為0,以保證結(jié)果準(zhǔn)確性。
(2) 通信接口使用Send/Recv,所以進程編號要管理好。另外,主進程只負責(zé)初始化矩陣及分發(fā)和匯總結(jié)果。
2. 實驗結(jié)果
(1) 設(shè)置為單進程,即串行文章來源:http://www.zghlxwxcb.cn/news/detail-479397.html
- 單進程命令行參數(shù)設(shè)置(如圖1)
圖 1 單進程時命令行參數(shù)設(shè)置 - 單進程運行結(jié)果,約97.20s(如圖2)
圖 2 單進程時運行的時間花銷
(2) 設(shè)置為2進程 - 2進程命令行參數(shù)設(shè)置(如圖3)
圖 3 程序2進程運行時命令行參數(shù)設(shè)置
- 2進程運行結(jié)果,約75.43s(如圖4)
圖 4 程序2進程時運行的時間花銷
(3) 設(shè)置為4進程 - 4進程命令行參數(shù)設(shè)置(如圖5)
圖 5 程序4進程運行時命令行參數(shù)設(shè)置
- 4進程運行結(jié)果,約17.17s(如圖6)
圖 6 程序4進程時運行的時間花銷
(4) 設(shè)置為8進程 - 8進程命令行參數(shù)設(shè)置(如圖7)
圖 7 程序8進程運行時命令行參數(shù)設(shè)置
- 8進程運行結(jié)果,約9.05s(如圖8)
圖 8 程序4進程時運行的時間花銷
由上述運行結(jié)果可得表格1
表格 1 程序運行結(jié)果分析表由表格1可知,隨著進程數(shù)的增加,程序運行時間也隨之減少,加速比隨之增加。但是,可以注意到,單進程和2進程的時間花銷相差并不大,2進程時的加速比僅為1.29,其原因是程序在多進程運行時,由于設(shè)計思路是主進程(0號進程)并不參與計算,只負責(zé)初始化矩陣和分發(fā)匯總結(jié)果,故2進程時程序優(yōu)化并不明顯。當(dāng)增加到4進程及8進程時,程序運行時間大大減少。其加速比曲線如圖9。
由于實驗要求的矩陣為2048*2048的雙精度浮點方陣,故在代碼完成后,先將矩陣維度設(shè)置為7,以檢驗程序結(jié)果是否準(zhǔn)確,同時對于生成的隨機數(shù)不設(shè)置種子,以保證每次程序運行的數(shù)據(jù)一致,從而確保實驗數(shù)據(jù)準(zhǔn)確,檢驗結(jié)果如圖10所示;圖 10 檢驗矩陣數(shù)據(jù)
四、實驗總結(jié):
經(jīng)過此次實驗,文章來源地址http://www.zghlxwxcb.cn/news/detail-479397.html
- 熟悉掌握了MPI幾個基本函數(shù),知曉Send/Recv通信接口的使用;
- 進一步了解MPI通信的相關(guān)原理;
- 掌握基本的MPI編程能力;
五、 附錄(代碼):
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <string>
#include "mpi.h"
#define NUM 2048 //矩陣大小
using namespace std;
//打印數(shù)組(測試使用)
void printfArray(double* A, int length) {
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
cout << A[i * length + j] << " " ;
}
cout << endl;
}
}
//擴展矩陣并且初始化,多余部分填充0
void matGene(double* A, int size, int actual_size) {
// actual size: 使用的矩陣極可能大于原始大小
for (int i = 0; i < actual_size; i++) {
for (int j = 0; j < actual_size; j++) {
if (i < size && j < size)
//初始化矩陣,隨機生成雙精度浮點數(shù)[-1,1]
A[i * actual_size + j] = -1.0 + 1.0 * rand() / RAND_MAX * 2; //A[i][j]
else A[i * actual_size + j] = 0; //擴展部分填充0
}
}
}
//計算矩陣乘法(進程數(shù)為1時)
void matMulti(double* A, double* B, double* C, int m, int n, int p) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < p; j++) {
C[i * p + j] = 0;
for (int k = 0; k < n; k++)
C[i * p + j] += A[i * n + k] * B[k * p + j];
}
}
}
//返回不大于根號(n)的最大因子
int factor(int n) {
double sqr_root = sqrt(n);
for (int i = sqr_root; i >= 1; i--) {
if (n % i == 0) return i;
}
}
int main(int argc, char* argv[]) {
int n = NUM; // 數(shù)組大小
double beginTime, endTime; //用于記錄時間
//srand((unsigned int)time(NULL)); //如果多次測試,可以注釋這一句
// 初始化MPI
int my_rank = 0, comm_sz = 0, namelen = 0;
char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); //獲取進程號
MPI_Comm_size(MPI_COMM_WORLD, &comm_sz); //獲取進程數(shù)
MPI_Get_processor_name(processor_name, &namelen); //獲得處理器名
MPI_Status status; //狀態(tài)
if (comm_sz == 1) { // no parallel
// Prepare data
double* A = new double[n * n + 2];
double* B = new double[n * n + 2];
double* C = new double[n * n + 2];
int saveN = n;
matGene(A, saveN, n);
matGene(B, saveN, n);
// 計算時間
beginTime = MPI_Wtime(); //開始時間
matMulti(A, B, C, n, n, n);
endTime = MPI_Wtime(); //結(jié)束時間
cout << processor_name << ":" << "單進程時間開銷:" << endTime - beginTime << "s" << endl;
//刪除矩陣
delete[] A;
delete[] B;
delete[] C;
}
else { // 進程數(shù)大于1時,啟用并行
int saveN = n;
// 主要過程負責(zé)收集結(jié)果,矩陣必須與散度相等:實際n大于輸入
//計算矩陣大?。ㄐ钄U展至負責(zé)計算的進程數(shù)的倍數(shù))即comm_sz-1的倍數(shù)
if (n % (comm_sz - 1) != 0) {
n -= n % (comm_sz - 1);
n += (comm_sz - 1);
}
/*
將可用于計算的進程數(shù)(comm_sz-1)分解為a*b
然后將全體行劃分為a個部分,全體列劃分為b個部分,
從而將整個矩陣劃分為size相同的(comm_sz-1)個塊。
每個子進程負責(zé)計算最終結(jié)果的一塊,只需要接收A對應(yīng)范圍的行和B對應(yīng)范圍的列,
而不需要把整個矩陣傳過去。
*/
int a = (comm_sz - 1) / factor(comm_sz - 1);
int b = factor(comm_sz - 1);
int each_row = n / a;
int each_column = n / b;
//0號進程負責(zé)初始化矩陣,分發(fā)和匯總結(jié)果
if (my_rank == 0) {
double* A = new double[n * n + 2];
double* B = new double[n * n + 2];
double* C = new double[n * n + 2];
// Prepare data
//cout << "n = " << n << endl;
//將矩陣的維度擴展到comm_sz-1的倍數(shù),多余的部分用0填充,保證正確性。
matGene(A, saveN, n);
matGene(B, saveN, n);
//計算開始時間
beginTime = MPI_Wtime();
//Send:發(fā)送數(shù)據(jù),矩陣A的行和矩陣B的列至各進程
//發(fā)送 A[beginRow:endRow, :], B[:, beginColumn:endColumn]
for (int i = 1; i < comm_sz; i++) { //發(fā)送數(shù)據(jù)到各進程(0號進程除外)
int beginRow = ((i - 1) / b) * each_row;
int beginColumn = ((i - 1) % b) * each_column;
// A: beginRow ~ endRow
MPI_Send(&A[beginRow * n + 0], each_row * n, MPI_DOUBLE, i, i, MPI_COMM_WORLD);
// B: n times, once a row
for (int j = 0; j < n; j++) {
MPI_Send(&B[j * n + beginColumn], each_column, MPI_DOUBLE, i, i * n + j + comm_sz + 2, MPI_COMM_WORLD);
}
}
//接受結(jié)果 Recv: C[beginRow:endRow, beginColumn:endColumn]
for (int i = 1; i < comm_sz; i++) {
int beginRow = ((i - 1) / b) * each_row;
int endRow = beginRow + each_row;
int beginColumn = ((i - 1) % b) * each_column;
for (int j = beginRow; j < endRow; j++) {
MPI_Recv(&C[j * n + beginColumn], each_column, MPI_DOUBLE, i, each_row * i + (j - beginRow), MPI_COMM_WORLD, &status);
}
}
endTime = MPI_Wtime(); //結(jié)束時間
//打印時間花銷
cout << processor_name << ":" << comm_sz << "個進程時間開銷:" << endTime - beginTime << "s" << endl;
//刪除矩陣
delete[] A;
delete[] B;
delete[] C;
}
else {
double* partA = new double[each_row * n + 2]; // A[beginRow:endRow, :]
double* partB = new double[n * each_column + 2]; // B[:, beginColumn:endColumn]
double* partC = new double[each_row * each_column + 2]; // C[beginRow:endRow, beginColumn:endColumn]
//各進程接受數(shù)據(jù) Recv: partA, partB
MPI_Recv(&partA[0 * n + 0], each_row * n, MPI_DOUBLE, 0, my_rank, MPI_COMM_WORLD, &status);
for (int j = 0; j < n; j++) {
MPI_Recv(&partB[j * each_column + 0], each_column, MPI_DOUBLE, 0, my_rank * n + j + comm_sz + 2, MPI_COMM_WORLD, &status);
}
//計算
matMulti(partA, partB, partC, each_row, n, each_column);
//發(fā)送計算之后的結(jié)果 Send: partC
for (int j = 0; j < each_row; j++) {
MPI_Send(&partC[j * each_column + 0], each_column, MPI_DOUBLE, 0, each_row * my_rank + j, MPI_COMM_WORLD);
}
//刪除數(shù)組
delete[] partA;
delete[] partB;
delete[] partC;
}
}
//終止MPI
MPI_Finalize();
return 0;
}
到了這里,關(guān)于矩陣乘法的MPI并行實驗報告的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!