多核环境下OpenMP并行编程
文章目录
一、实验环境
操作系统:Windows 10、Linux(Centos 7 虚拟机)
运行环境:Visual Stdudio 2019(cl)、Vim(g++)、VMware Workstation Pro
CPU处理器:AMD Zen2 3700x @3.8Ghz 8c16t ,4c4t(虚拟机)
二、实验内容
学习编制多线程并行程序实现如下功能:
1.创建多线程,输出线程号和线程数。
2.学习for多线程并行。
3.学习while多线程并行,实现全局共享变量存取。
4.编程实现大规模向量的并行计算
三、实验目的
1.掌握OpenMP并行编程基础;
2.掌握在Linux平台上编译和运行OpenMP程序;
3.掌握在Windows平台上编译和运行OpenMP程序。
4.用OpenMP实现最基本的矩阵乘法以及性能分析
四、实验步骤
4.1 Windos下编译并运行OpenMP程序
4.1.1 环境配置
在vs的(项目)(属性)(配置属性)(C/C++)(语言)中设置(OpenMP支持)为(是),并将其中的符合模式设置为(否)
4.1.2 代码
#include <omp.h>
#include <stdio.h>
int main() {
int nthreads, tid;
omp_set_num_threads(8);
#pragma omp parallel private(nthreads, tid)
{
tid = omp_get_thread_num();
printf("Hello World from OMP thread %d\n", tid);
if (tid == 0) {
nthreads = omp_get_num_threads();
printf("Number of threads is %d\n", nthreads);
}
}
}
4.1.3 运行结果
4.2 Linux平台上编译和运行OpenMP程序
4.2.1 环境配置:
安装g++:yum install g++
编译参数:g++ -fopenmp -o
测试代码同4.1.2windows环境下的代码
4.2.3 运行结果
4.3 多线程实现矩阵乘法
4.3.1 TimeCalculate() 计时函数
void TimeCalculate() {
static bool is_record = 1;
is_record = 1 - is_record;
static clock_t TimeStart = clock();
if (is_record == 0)
TimeStart = clock();
else {
const clock_t TimeEnd = clock();
std::cout << "This costs: ";
std::cout << (double)(TimeEnd - TimeStart) / CLK_TCK * 1000;
std::cout << " ms." << std::endl;
}
}
通过static全局静态变量,可以实现运行该函数开始计时,再次运行停止计时,并输出时间花费。
4.3.2 Matrix 矩阵类和矩阵乘法
class Matrix {
public:
int rows; // 行
int cols; // 列
float* elements;
Matrix(int rows, int cols, float v) :rows(rows), cols(cols) {
elements = new float[rows * cols];
for (int i = 0; i < cols * rows; i++) elements[i] = v;
}
Matrix(int rows, int cols, float *v) :rows(rows), cols(cols) {
elements = new float[rows * cols];
for (int i = 0; i < cols * rows; i++) elements[i] = v[i];
}
~Matrix() {
delete[] elements;
}
};
void MatMul(Matrix& A, Matrix& B, Matrix& ret) {
for (int i = 0; i < A.rows; i++) {
for (int j = 0; j < B.cols; j++) {
float t = 0;
for (int k = 0; k < A.cols; k++)
t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
ret.elements[i * ret.cols + j] = t;
}
}
}
Matrix
矩阵类提供了两种构造方式,分别为(行,列,元素值)和(行,列,元素值数组)
MatMul()
函数传入三个矩阵引用,分别是矩阵A、矩阵B和结果矩阵ret
,通过三层循环得到结果矩阵ret
4.3.3 并行实现多次矩阵乘法
void test1(int t_num) {
Matrix A(4, 4, 1), B(4, 4, 2), C(4, 4, 0.0);
int per = 10000000 / t_num;
omp_set_num_threads(t_num);
int t, i;
#pragma omp parallel for
for (t = 0; t < t_num; t++) {
for (i = 0; i < per; i++) {
MatMul(A, B, C);
}
}
}
传入参数为使用的线程数t_num
,该函数计算了1000万次4*4的矩阵乘法,使用了t_num
个线程进行并行,每个线程计算10000000 / t_num
次矩阵乘法,当t_num
等于1时,原函数相当于串行计算。
4.3.4 并行实现单次大矩阵乘法
重写MatMul()
函数
void MatMul2(Matrix& A, Matrix& B, Matrix& ret, int t_num) {
omp_set_num_threads(t_num);
#pragma omp parallel default(shared)
{
int id = omp_get_thread_num();
for (int i = 0; i < A.rows; i++) {
if (i % t_num != id) continue;
for (int j = 0; j < B.cols; j++) {
float t = 0;
for (int k = 0; k < A.cols; k++)
t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
ret.elements[i * ret.cols + j] = t;
}
}
}
}
MatMul2()
函数传入三个矩阵引用和线程数t_num
,矩阵分别是矩阵A
、矩阵B
和结果矩阵ret
,先调用omp_set_num_threads()
初始化并行线程个数,通过最外层的同余判断,使得线程id
计算第i%t_num==id
轮循环,从而达到并行计算的效果。
void test2(int t_num) {
Matrix A(2000, 2000, 3), B(2000, 2000, 2), C(2000, 2000, 0.0);
MatMul2(A, B, C, t_num, A.cols * A.elements[0] * B.elements[0]);
}
传入参数为使用的线程数t_num
,该函数计算通过调用MatMul2()
函数,使用t_num
个线程并行计算两个2000*2000的矩阵。
五、性能分析
5.1 加速比
加速比(speedup)是同一个任务在单处理器系统和并行处理器系统中运行消耗的时间的比率,用来衡量并行系统或程序并行化的性能和效果。
加速比 S p S_p Sp以如下公式定义: S p = T 1 T p S_p=\frac{T_1}{T_p} Sp=TpT1
其中 p p p 指CPU数量, T 1 T_1 T1指顺序执行的执行时间, T p T_p Tp指当有 p p p个处理器时,并行算法执行的时间
5.2 并行实现多次矩阵乘法性能
5.3 并行实现单次大矩阵乘法性能
5.4 性能分析
通过上面四张图我们可以看出随着线程数的增加,运行时间先减小后增大,并在16线程时取到最小值;加速比先增大后减小,同样在16线程时取到最大值。线程数量在1至16时,线程数量没增加一倍,运行耗时和加速比变为原来的二分之一多,加速比变为原来的两倍不到,而在超过16线程时,运行时间不再继续下降,反而还会略有上升,加速比同理。
可能的原因分析:上述实验是在AMD Zen2 3700x @3.8Ghz 8c16t的硬件环境下完成的,这颗CPU共有8个物理核心,16个线程(下图为任务管理器截图)
因此在使用和处理器相同的16线程进行测试时,会取得最大的运行效率,而在线程数量超过16时,前16线程组线程并行,但其余线程需要等待还未结束的线程让出资源才能开始启动,相较于前16线程是串行执行的,而线程的切换还需要耗费额外时间,因此反而可能不及仅有16线程的效率。
六、实现感想
在本次实验我学了解了OpenMP语句的基本语法和用法。相较于CUDA并行编程,OpenMP实现起来比较简单,仅需要通过预编译指令以及一些简单的库函数就可以完成复杂的并行计算,在如今处理器向多核发展的大环境下,并且带来十分不错的计算效率和加速比。此外要注意在OpenMP的编程过程中,使用的线程数量不宜超过实体CPU的线程数量,否则会带来效率上的下降。最后也不是所有程序都需要用到并行计算,部分小规模运算使用并行反而会增大开销,而另一部分则只能通过串行来解决。
七:附录(完整测试代码)
#include <omp.h> // OpenMP编程需要包含的头文件
#include <bits/stdc++.h>
using namespace std;
void TimeCalculate() {
static bool is_record = 1;
is_record = 1 - is_record;
static clock_t TimeStart = clock();
if (is_record == 0)
TimeStart = clock();
else {
const clock_t TimeEnd = clock();
std::cout << "This costs: ";
std::cout << (double)(TimeEnd - TimeStart) / CLK_TCK * 1000;
std::cout << " ms." << std::endl;
}
}
class Matrix {
public:
int rows; // 行
int cols; // 列
double* elements;
Matrix(int rows, int cols, double v) :rows(rows), cols(cols) {
elements = new double[rows * cols];
for (int i = 0; i < cols * rows; i++) elements[i] = v;
}
Matrix(int rows, int cols, double *v) :rows(rows), cols(cols) {
elements = new double[rows * cols];
for (int i = 0; i < cols * rows; i++) elements[i] = v[i];
}
~Matrix() {
delete[] elements;
}
};
void MatMul1(Matrix& A, Matrix& B, Matrix& ret) {
for (int i = 0; i < A.rows; i++) {
for (int j = 0; j < B.cols; j++) {
double t = 0;
for (int k = 0; k < A.cols; k++)
t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
ret.elements[i * ret.cols + j] = t;
}
}
}
void MatMul2(Matrix& A, Matrix& B, Matrix& ret, int t_num, double v) {
omp_set_num_threads(t_num);
#pragma omp parallel default(shared)
{
int id = omp_get_thread_num();
for (int i = 0; i < A.rows; i++) {
if (i % t_num != id) continue;
for (int j = 0; j < B.cols; j++) {
double t = 0;
for (int k = 0; k < A.cols; k++)
t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
ret.elements[i * ret.cols + j] = t;
}
}
}
int f = 1;
for (int i = 0; i < ret.cols * ret.rows; i++) {
if (abs(ret.elements[i] - v) > 1e-2) {
cout << i / ret.cols << " " << i % ret.cols << " " << v << " " << ret.elements[i] << endl;
}
}
if (!f) cout << "error!\n";
}
double getrd() {
return 10.0 * rand() / RAND_MAX;
}
void test1(int t_num) {
Matrix A(4, 4, getrd()), B(4, 4, getrd()), C(4, 4, 0.0);
int per = 10000000 / t_num;
omp_set_num_threads(t_num);
int t, i;
#pragma omp parallel for
for (t = 0; t < t_num; t++) {
for (i = 0; i < per; i++) {
MatMul1(A, B, C);
}
}
}
void test2(int t_num) {
Matrix A(2000, 2000, getrd()), B(2000, 2000, getrd()), C(2000, 2000, 0.0);
MatMul2(A, B, C, t_num, A.cols * A.elements[0] * B.elements[0]);
}
int main() {
for (int i = 1; i <= 128; i *= 2) {
TimeCalculate();
cout << "Thread num: " << i << endl;
test1(i);
TimeCalculate();
}
for (int i = 1; i <= 128; i *= 2) {
TimeCalculate();
cout << "Thread num: " << i << endl;
test2(i);
TimeCalculate();
}
}