以下內容爲本人並行計算課程的期末作業,有不足的地方,請多多指教!
1 實驗目的
本實驗的目的主要有以下三點:
1、 實現方陣行列式的計算。
2、 實現方陣行列式的並行計算,分別基於 OpenMP和 MPI。
3、 比較以上三種算法的運行時間,計算加速比。
2 實驗設計
2.1 生成方陣
爲方便,本實驗的方陣不採取手動輸入的方式,而是使用隨機數來生成矩陣元素。
我定義了一個全局方陣變量——int p[100][100]。在創建方陣時,方陣的階數N(N<100)由外部輸入。然後用兩層“for循環”來給方陣 p左上角 N×N個位置賦值。具體實現如下:
/*
* 定義矩陣階數N
*/
int N;
/*
* 定義一個全局矩陣
*/
int p[100][100];
/*
* 用隨機數生成矩陣
*/
void create(){
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
{
int a=rand()%15;//產生隨機數,並賦給數組
p[i][j]=a;
}
}
}
2.2 打印矩陣
將生成的矩陣輸出,以便驗算其計算行列式的正確性。具體實現如下:
/*
* 輸出矩陣
*/
void print()
{
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
printf("%d ",p[i][j]);
printf("\n");
}
}
2.3 計算矩陣行列式
計算矩陣行列式的方法有很多,本實驗選擇的方法是:行列式按行展開法。行列式等於它任一行的各元素與其對應的代數餘子式乘積之和。代數餘子式:A(ij)=(-1)^(i+j)M(ij). (ij)爲下標。某個元素的餘子式等於原行列式劃去該元素所在的行和列。本實驗採取按第一行展開的方法。即:將高階的行列式按第一行展開,一直重複展開行爲,直到階數爲 1。上述過程可用遞歸完成。
2.3.1 遞歸實現代碼
根據上面的理論,我們容易得出如下的實現方法:
/*
* 計算行列式的函數
*/
long long mydet(int p [100][100],int n){
if(n==1) //n=1返回矩陣的唯一數,停止遞歸
return p[0][0];
else{
long long sum=0;
for(int i=0;i<n;i++)
{
int pp[100][100];//用於存放少一維的矩陣,爲方便直接定義爲100×100.
for(int j=1,j1=0;j<n;j++)//去掉第一行
{
for(int k=0,k1=0;k<n;k++)
{
if(k==i)
;//去掉對應的列
else
{
pp[j1][k1]=p[j][k];//pp爲餘子式
k1++;
}
}
j1++;
}
if(i%2)
sum+=(-1)*p[0][i]*mydet(pp,n-1);
else
sum+=p[0][i]*mydet(pp,n-1);
}
return sum;
}
}
2.4 實現串行\OpenMP\MPI計算
我這裏的並行主要是放在第一次的按行展開那,具體實現看代碼吧。
2.4.1 串行代碼
/*************************************************************************
> File Name: matrix_det.c
> Author: surecheun
> Mail: [email protected]
> Created Time: 2017年12月06日 星期三 17時28分00秒
************************************************************************/
#include<stdlib.h>
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<time.h>
/*
* 定義矩陣階數N
*/
int N;
/*
* 定義一個全局矩陣
*/
int p[100][100];
/*
* 用隨機數生成矩陣
*/
void create(){
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
{
int a=rand()%15;//產生隨機數,並賦給數組
p[i][j]=a;
}
}
}
/*
* 輸出矩陣
*/
void print()
{
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
printf("%d ",p[i][j]);
printf("\n");
}
}
/*
* 計算行列式的函數
*/
long long mydet(int p [100][100],int n){
if(n==1) //n=1返回矩陣的唯一數,停止遞歸
return p[0][0];
else{
long long sum=0;
for(int i=0;i<n;i++)
{
int pp[100][100];//用於存放少一維的矩陣,爲方便直接定義爲100×100.
for(int j=1,j1=0;j<n;j++)//去掉第一行
{
for(int k=0,k1=0;k<n;k++)
{
if(k==i)
;//去掉對應的列
else
{
pp[j1][k1]=p[j][k];//pp爲餘子式
k1++;
}
}
j1++;
}
if(i%2)
sum+=(-1)*p[0][i]*mydet(pp,n-1);
else
sum+=p[0][i]*mydet(pp,n-1);
}
return sum;
}
}
int main(){
printf("N= ");
scanf("%d",&N);
while(N){ //如果輸入N就可以繼續算下去,這個設計主要爲了方便獲取時間數據來計算平均用時
create();
print();
clock_t start_t=clock(); //開始計時
printf("the sum of 串行 is %lld .\n",mydet(p,N));
clock_t end_t=clock(); //結束計時
double runing_t =(double)(end_t-start_t)/CLOCKS_PER_SEC;
printf("the runing time of 串行 is %f s.",runing_t); //輸出時間
printf("\n");
printf("N= ");
scanf("%d",&N);
}
return 0;
}
2.4.2 OpenMP代碼
/*************************************************************************
> File Name: matrix_det_omp.c
> Author: surecheun
> Mail: [email protected]
> Created Time: 2017年12月07日 星期四 17時23分51秒
************************************************************************/
#include<stdlib.h>
#include<stdio.h>
#include<math.h>
#include<vector>
#include<time.h>
#include<omp.h>
/*
* 定義線程數
*/
#define n_threads 2
/*
*定義矩陣的階數爲全局變量
*/
int N;
/*
* 定義一個全局矩陣
*/
int p[100][100];
/*
* 用隨機數生成矩陣
*/
void create(){
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
{
int a=rand()%15;//產生隨機數,並賦給數組
p[i][j]=a;
}
}
}
/*
* 輸出矩陣
*/
void print()
{
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
printf("%d ",p[i][j]);
printf("\n");
}
}
/*
* 計算行列式的函數
*/
long long mydet(int p [100][100],int n){
if(n==1) //n=1返回矩陣的唯一數,停止遞歸
return p[0][0];
else{
long long sum=0;
for(int i=0;i<n;i++)
{
int pp[100][100];
for(int j=1,j1=0;j<n;j++)//去掉第一行
{
for(int k=0,k1=0;k<n;k++)
{
if(k==i)//去掉和改數相同的列
;
else
{
pp[j1][k1]=p[j][k]; //pp爲代數餘子式
k1++;
}
}
j1++;
}
if(i%2)
sum+=(-1)*p[0][i]*mydet(pp,n-1);
else
sum+=p[0][i]*mydet(pp,n-1);
}
return sum;
}
}
int main(){
printf("N= ");
scanf("%d",&N);
while(N){ //如果輸入的N>0,則繼續計算
create(); //創建矩陣
print(); //打印創建的矩陣
double start1,finish1;
start1=omp_get_wtime(); //開始計算時間
long long sum=0;
omp_set_num_threads(n_threads);//設置線程數
#pragma omp parallel for reduction(+:sum)//並行化
for(int i=0;i<N;i++)
{
int pp[100][100];
for(int j=1,j1=0;j<N;j++)//去掉第一行
{
for(int k=0,k1=0;k<N;k++)
{
if(k==i)//去掉和i相同的列
;
else
{
pp[j1][k1]=p[j][k]; //pp爲餘子式
k1++;
}
}
j1++;
}
if(i%2)
sum+=(-1)*p[0][i]*mydet(pp,N-1);
else
sum+=p[0][i]*mydet(pp,N-1);
}
printf("the sum of omp is %lld .\n",sum);//輸出結果
finish1=omp_get_wtime(); //結束計算時間
double runing_t =finish1-start1;
printf("the runing time of opm is %f s.",runing_t);//輸出時間
printf("\n");
printf("N= ");
scanf("%d",&N);
}
return 0;
}
2.4.3 MPI實現代碼
/*************************************************************************
> File Name: matrix_det_mpi.c
> Author: surecheun
> Mail: [email protected]
> Created Time: 2017年12月07日 星期四 16時24分03秒
************************************************************************/
#include<stdlib.h>
#include<stdio.h>
#include<math.h>
#include<time.h>
#include<mpi.h>
/*
*定義矩陣的階數爲全局變量
*/
int N;
/*
* 定義一個全局矩陣
*/
int p[100][100];
/*
* 用隨機數生成矩陣
*/
void create(){
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
{
int a=rand()%15;//產生隨機數,並賦給數組
p[i][j]=a;
}
}
}
/*
* 輸出矩陣
*/
void print()
{
int i,j;
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
printf("%d ",p[i][j]);
printf("\n");
}
}
/*
* 計算行列式的函數
*/
long long mydet(int p [100][100],int n){
if(n==1) //n=1返回矩陣的唯一數,停止遞歸
return p[0][0];
else{
long long sum=0;
for(int i=0;i<n;i++)
{
int pp[100][100];
for(int j=1,j1=0;j<n;j++)//去掉第一行
{
for(int k=0,k1=0;k<n;k++)
{
if(k==i)
;
else
{
pp[j1][k1]=p[j][k];
k1++;
}
}
j1++;
}
if(i%2)
sum+=(-1)*p[0][i]*mydet(pp,n-1);
else
sum+=p[0][i]*mydet(pp,n-1);
}
return sum;
}
}
int main(int argc,char *argv[]){
scanf("%d",&N);
int num_procs,my_rank;
double start = 0.0, stop = 0.0; //記錄時間的變量
long long per_procs = 0.0; //記錄每個進程算的和
long long result = 0.0; //矩陣行列式結果
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
if (my_rank == 0)
{//0號線程創建矩陣
create();
print(); //打印創建的矩陣
}
start = MPI_Wtime(); //開始計算時間
MPI_Bcast(&N, 1, MPI_INT, 0, MPI_COMM_WORLD); //將矩陣大小廣播給所有進程
for (int i = 0; i <N; i++)
{
MPI_Bcast(p[i],N, MPI_INT, 0, MPI_COMM_WORLD);
} //將矩陣廣播給所有進程
for (int i = my_rank; i <N; i += num_procs){ //每個線程處理不同的行和列
long long sum_i=0;
int pp[100][100];
for(int j=1,j1=0;j<N;j++)//去掉第一行
{
for(int k=0,k1=0;k<N;k++)
{
if(k==i)
;
else
{
pp[j1][k1]=p[j][k];
k1++;
}
}
j1++;
}
if(i%2)
sum_i=(-1)*p[0][i]*mydet(pp,N-1);
else
sum_i=p[0][i]*mydet(pp,N-1);
per_procs += sum_i; //記錄每個進程的和
}
MPI_Reduce(&per_procs, &result, 1, MPI_LONG_LONG_INT, MPI_SUM, 0, MPI_COMM_WORLD);//在0號進程求總和
if (my_rank == 0){
printf("the sum of mpi is %lld .\n",result) ;
stop = MPI_Wtime(); //結束計算時間
printf("the time of mpi is %f s\n", stop - start);
fflush(stdout);
}
MPI_Finalize();
return 0;
}
4 實驗結果
4.1 正確性
4.1.1串行
結果分析,以 n=4爲例,輸出的矩陣爲:
13 | 1 | 12 | 10 |
8 | 10 | 1 | 12 |
9 | 1 | 2 | 7 |
5 | 4 | 8 | 1 |
輸出結果爲:3875
和matlab計算結果一致!
4.1.2 OpenMP
結果分析,以 n=4爲例,輸出的矩陣爲:
5 | 0 | 8 | 1 |
1 | 5 | 11 | 3 |
2 | 5 | 1 | 1 |
0 | 0 | 14 | 12 |
輸出結果爲:-2710
和matlab計算結果一致!
4.1.3 MPI
結果分析,以n=4爲例,輸出矩陣爲:
9 | 1 | 2 | 7 |
5 | 4 | 8 | 1 |
0 | 6 | 7 | 1 |
11 | 8 | 12 | 9 |
輸出結果爲:-202
和matlab計算結果一致!
4.2 加速比
通過多次求平均,得到三種計算實現方法的計算時間(保留 3 位有效數字)如下:
N(階數) | 串行 |
OpenMP |
MPI |
9 |
0.0239s |
0.0117s |
0.0117s |
10 |
0.195s |
0.105s |
0.100s |
柱狀圖如下: