矩阵乘法
1. 需求
矩阵乘法AB=C。其中,A,B,C均为20482048双精度浮点方阵,A,B初始值为[-1,1]的随机数。
2.串行和OpenMP并行代码
#include <iostream>
#include <stdlib.h>
#include <ctime>
#include "hpc_helpers.hpp"
using namespace std;
#define NUM 2048 //表示数组的长度
//由于数组过大,所以需要定义全局变量,否则容量不够
double A[NUM][NUM];
double B[NUM][NUM];
double C[NUM][NUM];//初始化
void init (double A[][NUM] , double B[][NUM]) {srand ((unsigned int)time (NULL));for ( int row = 0; row < NUM; row++ ) {for ( int col = 0; col < NUM; col++ ) {//x + 1.0 * rand() / RAND_MAX * ( y - x )//随机生成[-1,1]的随机双精度浮点数A[row][col] = -1.0 + 1.0 * rand () / RAND_MAX * 2;A[row][col] = -1.0 + 1.0 * rand () / RAND_MAX * 2;}}
}//计算(矩阵乘法)
void mult (double A[][NUM] , double B[][NUM] , double C[][NUM] , bool parallel , int num_thread) {
#pragma omp parallel for num_threads(num_thread)if(parallel)//使用三重for循环完成矩阵的乘法for ( int i = 0; i < NUM; i++ ) { //完成行结果的循环(相对结果C矩阵)for ( int j = 0; j < NUM; j++ ) { //完成列结果的循环(相对结果C矩阵)double accum = 0;for ( int k = 0; k < NUM; k++ ) { //最内层循环,负责完成A的行乘以B的列//A的第i行的数分别乘以B的第j列的相应数,即矩阵乘法(k表示“相应数”的位置)accum += A[i][k] * B[k][j];}C[i][j] = accum;}}
}
int main () {TIMERSTART (overall) //计算花费的总时间cout << "=========初始化花费时间=========" << endl;//计算初始化花费的时间TIMERSTART (init)init (A , B); //初始化TIMERSTOP (init)cout << "=========串行花费时间=========" << endl;//计算3次串行花费的时间for ( int i = 0; i < 3; i++ ) {TIMERSTART (mult_seq)mult (A , B , C , false , 1);TIMERSTOP (mult_seq)}//计算3次并行花费的时间(分别2、4、8、16、32线程)for ( int i = 2; i < 33;i=i*2) {cout << "=========并行花费时间=========" << endl;cout << "当前线程数:" << i << endl;for ( int j = 0; j < 3; j++ ) {TIMERSTART (mult_par)mult (A , B , C , true , i);TIMERSTOP (mult_par)}}TIMERSTOP (overall)return 0;
}
代码分析:
首先,引入了一个定制的头文件hpc_helpers.hpp用于测量程序各部分的时间花销。因为涉及的二维数组比较大(2048*2048),所以需要将所用数组定义为全局变量,否则会因为栈容量不够而报错。
其次,将数组的初始化封装为init函数,其中会将数组以随机双精度浮点数初始化,随机数的范围是[-1,1]。而矩阵乘法的关键计算部分被封装为mult函数,其中将利用三重for循环实现矩阵乘法,程序的“并行”代码是‘#pragma omp parallel for num_threads(num_thread)if(parallel)’。mult函数中的参数parallel (bool)用于在串行和并行执行模式之间切换,参数num_thread(int)则是设置程序并行运行时的线程数。
最后,以同一份数据分别执行并行模式下的2、4、8、16、32个线程情况。同时,为了保证实验数据的可靠性,程序将会把随机生成的矩阵分别计算三次(串行和并行),并且打印出每次计算的时间花销。
性能分析
图1显示了分别在串行模式和并行模式(2、4、8、16、32个线程)运行的时间花销,其中,elapsed time (init)表示初始化花费的时间,elapsed time (mult_seq) 表示串行模式花费的时间,elapsed time (mult_par) 表示并行模式下花费的时间,elapsed time (overall)表示程序总共花费的时间。
图 1 程序运行结果截图
图2为程序运行时cpu线程数和运行窗口的截图,显示了程序设置的运行时线程数与系统分配给程序的线程数是匹配的。
图 2 系统线程数截图
由图1可总结出表格1。
**线程数P/**个 | 平均花销时间T/s | 加速比S | 相应并行效率E |
---|---|---|---|
1**(串行)** | 61.93 | / | / |
2 | 32.58 | 1.90 | 95% |
4 | 18.46 | 3.35 | 84% |
8 | 9.76 | 6.35 | 80% |
16 | 5.91 | 10.48 | 66% |
32 | 5.96 | 10.39 | 32% |
图 3 加速比曲线图
由表格1和图3可知,程序的加速比随着线程数的增加而增大,其原因在于更大数目的线程数需要更多的额外通信开销,相应的并行效率随着线程数的增加而减小。同时,注意到线程数为32时,加速比反而是略微下降。原因可能是实验的笔记本电脑cup为8核16线程,而一般并行设置的线程数最大为(核心数*2-1),所以当线程数为32时导致的电脑卡顿从而增加了时间。