本文主要记录了基于OpenMP和PThread实现不同线程、不同矩阵大小的矩阵乘法实例及其分析,代码注释比较详细,以供参考。
相关代码也可以直接访问git,这里贴上链接:
https://github.com/zly5/Parallel-Computing-Labhttps://github.com/zly5/Parallel-Computing-Lab
在这之前先让我们搞明白两个个名词——加速比和效率:
加速比:是同一个任务在单处理器系统和并行处理器系统中运行消耗的时间的比率,用来衡量并行系统或程序并行化的性能和效果。其定义如下式所示:
其中,Sp是加速比,T1是单处理器下的运行时间,Tp是在有p个处理器并行系统中的运行时间。当Sp=p时,此加速比被称为线性加速比。
效率: 反映了并行系统中处理器的利用情况,其定义用下式来表示。
其中Ep表示效率,Sp表示加速比,p表示处理器个数。
基于OpenMP并行矩阵与矢量乘法实验
🤗先沾上完整代码如下:
#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
#include <time.h>
const int NUM_THREADS = 4; //设置线程数量
int N = 10000;
int M = 10000;
int mat[10000][10000]; //矩阵mat
int vec[10000], ans[10000]; //向量vec
void makeRandomMatrix() //生成矩阵
{
srand(time(NULL));
int i, j;
for (i = 0; i < M; i++)
{
for (j = 0; j < N; j++)
{
mat[i][j] = rand() % 10 + 1;
}
}
}
void makeRandomVector() //生成向量
{
srand(time(NULL));
int i;
for (i = 0; i < N; i++)
{
vec[i] = rand() % 10 + 1;
}
}
void funy(int a[], int cur) //计算矩阵和矢量乘的部分结果
{
int i;
for (i = 0; i < N; i++)
{
ans[cur] += a[i] * vec[i];
}
}
void f() //串行计算
{
int i;
for (i = 0; i < M; i++)
{
funy(mat[i], i);
}
}
void fp() //并行计算
{
int i;
#pragma omp parallel for num_threads(NUM_THREADS)
for (i = 0; i < M; i ++)
{
funy(mat[i], i);
}
}
int main()
{
printf("Makeing matrix(%d*%d) & vector(%d*1)...\n",N,M,N);
makeRandomMatrix();
makeRandomVector();
double start_time = omp_get_wtime();
f();
double end_time = omp_get_wtime();
printf("串行 --- Running time=%f s\n", end_time - start_time);
start_time = omp_get_wtime();
fp();
end_time = omp_get_wtime();
printf("%d threads --- Running time=%f s\n", NUM_THREADS,end_time - start_time);
return 0;
}
首先,我们考虑小矩阵(200*200)和小矢量(200)即计算量不大的情况下,多线程和串行的比较,其结果如下表1所示。
表1 200大小矩阵矢量乘法测试
串行程序 | 2线程 | 4线程 | |
第一次 | 0.000277 | 0.000279 | 0.000286 |
第二次 | 0.000276 | 0.000270 | 0.000319 |
第三次 | 0.000275 | 0.000270 | 0.000268 |
第四次 | 0.000274 | 0.000268 | 0.000356 |
第五次 | 0.000276 | 0.000269 | 0.000315 |
平均值 | 0.0002756 | 0.0002712 | 0.0003088 |
从以上结果可以看到,在计算规模不大时,多线程程序相比串行串行优势不大,其更大的时间花在了线程调度相关任务。因此,接下来,我们考虑大矩阵(10000*10000)和大矢量(10000)即计算量较大的情况下,多线程和串行的比较,其结果如下表2和图1所示。
表2 10000大小矩阵矢量乘法测试
串行程序 | 2线程 | 4线程 | |
第一次 | 0.216982 | 0.124177 | 0.070351 |
第二次 | 0.216612 | 0.124312 | 0.072720 |
第三次 | 0.216488 | 0.127765 | 0.071641 |
第四次 | 0.246336 | 0.127330 | 0.071734 |
第五次 | 0.216362 | 0.123395 | 0.069704 |
平均值 | 0.222556 | 0.1253958 | 0.07123 |
图1 10000大小矩阵矢量乘法测试结果
从表2和图1中我们可以看到在计算规模较大时,多线程才可以发挥出自己最大的能力,表2中,2线程比串行快将近一倍,4线程又比2线程快将近一倍。
具体的加速比和效率数据如下表3所示;
表3 10000大小矩阵矢量乘法加速比和效率
串行程序 | 2线程 | 4线程 | |
平均用时 | 0.222556 | 0.1253958 | 0.07123 |
加速比 | 1 | 1.774828184 | 3.124470027 |
效率 | 100% | 88.7414092% | 78.1117507% |
从上表3的数据及表1的数据来看,多线程的加速比和效率都和处理子问题的规模有关,若处理的规模太小,创建的线程太多,那多线程的串行将在线程调度等操作上耗费大量时间。
基于OpenMP并行矩阵乘法实验
🤗这里就只沾上完整代码了,具体的记时和测试可以和上例相同操作:
#include <iostream>
#include <pthread.h>
#include <omp.h>
#include <sys/time.h>
#include <ctime>
using namespace std;
const int maxn = 4;
int A[maxn][maxn], B[maxn][maxn], C[maxn][maxn];
int main() {
int i, j, k;
omp_set_num_threads(omp_get_num_procs());
srand(time(NULL));
for (i = 0; i < maxn; i++)
for (j = 0; j < maxn; j++) {
A[i][j] = rand() % 10;
B[i][j] = rand() % 10;
}
#pragma omp parallel for private(i,j,k) shared(A,B,C)
for (i = 0; i < maxn; ++i)
for (j = 0; j < maxn; ++j)
for (k = 0; k < maxn; ++k){
//printf("OpenMP Test, : %d\n", omp_get_thread_num());
C[i][j] += A[i][k] * B[k][j];
}
for (i = 0; i < maxn; i++) {
for (j = 0; j < maxn; j++)
cout << C[i][j] << "\t";
cout << endl;
}
}
PThread矩阵相乘实验
考虑到矩阵乘法的算法过程,我们按如下思想去设计多线程程序,给定线程数NUM_THREADS和矩阵matrixA(M*N)、matrixB(N*M)。其中每一个线程计算矩阵A中的L=M/NUM_THREADS行数据和B中的所有数据相乘的结果。
🤗同样先沾上完整代码如下:
#include<stdio.h>
#include<time.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<memory.h>
#define M 600
#define N 600
int matrixA[M][N];
int matrixB[N][M];
int result[M][N];
void *func(void *arg);
const int NUM_THREADS =8 ; //线程数
pthread_t tids[NUM_THREADS]; //线程
int L; //每个线程计算的块大小
void makeRandomMatrix_A() //生成矩阵
{
srand(time(NULL));
int i, j;
for (i = 0; i < M; i++)
{
for (j = 0; j < N; j++)
{
matrixA[i][j] = rand() % 10 + 1;
}
}
}
void makeRandomMatrix_B() //生成矩阵
{
srand(time(NULL));
int i, j;
for (i = 0; i < N; i++)
{
for (j = 0; j < M; j++)
{
matrixB[i][j] = rand() % 10 + 1;
}
}
}
//子线程函数
void *func(void *arg)
{
int s=*(int *)arg; //接收传入的参数(此线程从哪一行开始计算)
int t=s+L; //线程算到哪一行为止
for(int i=s;i<t;i++)
for(int j=0;j<M;j++)
for(int k=0;k<N;k++)
result[i][j]+=matrixA[i][k]*matrixB[k][j];
}
void fp(){ //串多线程函数
int i;
int j = 0;
int t[NUM_THREADS]; //传参索引
L = M / NUM_THREADS; //按设置的线程数分配工作块(单个线程所要计算的行数L)
for(i=0;i<M;i+=L)
{
t[j] = i;
if (pthread_create(&tids[j], NULL, func, (void *)&(t[j]))) //产生线程,去完成矩阵相乘的部分工作量
{
perror("pthread_create");
exit(1);
}
j++;
}
for(i=0;i<NUM_THREADS;i++)
pthread_join(tids[i],NULL); //等所有的子线程计算结束
}
void f(){ //串行程序函数
int res[M][M]={0}; //保存矩阵相乘的结果。非全局变量一定要显示初始化为0,否则为随机的一个数
for(int i=0;i<M;i++)
for(int j=0;j<M;j++)
for(int k=0;k<N;k++)
res[i][j]+=matrixA[i][k]*matrixB[k][j];
}
int main()
{
makeRandomMatrix_A(); //用随机数产生两个待相乘的矩阵,并分别存入两个文件中
makeRandomMatrix_B(); //从两个文件中读出数据赋给matrixA和matrixB
printf("Makeing matrix(%d*%d) & matrix(%d*%d)...\n",N,M,M,N);
//串行计算
clock_t start2=clock(); //开始计时
f(); //串行程序
clock_t finish2=clock(); //结束计算
printf("串行 --- Running time=%f s\n", (double)(finish2 - start2) / CLOCKS_PER_SEC);
//多线程计算
clock_t start1=clock(); //开始计时
fp(); //多线程
clock_t finish1=clock(); //结束计算
printf("%d threads --- Running time=%f s\n", NUM_THREADS,(double)(finish1 - start1) / CLOCKS_PER_SEC);
return 0;
}
使用以上程序开始测试,我们测试了矩阵大小为200*200,400*400,600*600时串行程序和不同的多线程程序对应的计算时间分别为多少。具体结果如下表4、5、6所示。
表4 200*200大小矩阵乘法实验结果
线程数 | 时间 | 加速比 | 效率 |
1 | 0.018000 | 1 | 100% |
2 | 0.010000 | 1.800000 | 90.0000% |
4 | 0.008000 | 2.250000 | 56.2500% |
10 | 0.007000 | 2.571429 | 25.7143% |
线程数 | 时间 | 加速比 | 效率 |
1 | 0.139000 | 1 | 100% |
2 | 0.087000 | 1.597701 | 79.8851% |
4 | 0.038000 | 3.657895 | 91.4474% |
8 | 0.032000 | 4.343750 | 54.2969% |
表6 800*800大小矩阵乘法实验结果
线程数 | 时间 | 加速比 | 效率 |
1 | 0.494000 | 1 | 100% |
2 | 0.322000 | 1.534161 | 76.7081% |
4 | 0.144000 | 3.430556 | 85.7639% |
8 | 0.115000 | 4.295652 | 53.6957% |
从上述表格中我们可以看到,随着问题规模的增大,无论是单线程即串行程序,还是2线程、4线程乃至8线程的计算时间都将增大。对于同等规模的问题,多线程的表现性能依然和此问题的计算规模有关系,当每一个子线程的计算开销小于线程调度等开销时,其加速比和效率往往表现不佳。