1. 目标

设计一个程序,使用OpenMP并行化实现矩阵乘法。给定两个矩阵 A 和 B,矩阵大小均为1024*1024,你的任务是计算它们的乘积 C。
要求:
(1)、使用循环结构体的知识点,包括for循环体并行化、变量规约属性与子句reduction、循环调度策略与子句schedule以及嵌套循环与子句collapse。
(2)、实现OpenMP并行化以加速矩阵乘法的计算。
(3)、考虑内存一致性,确保数据在并行计算过程中的正确性。
(4)、可选:实现线程亲核性,将线程绑定到特定的CPU核心上执行。

2. 测试简介

使用对比串行和并行,及 collapse 和 reduction 的效率。

3. 测试代码

#include <stdio.h>
#include <omp.h>
#include <sys/time.h>
#include <stdlib.h> void gemm_1(int* A,int* B,long* C,int N,int NT);
void gemm_2(int* A,int* B,long* C,int N,int NT);
void gemm_3(int* A,int* B,long* C,int N,int NT);
void gemm_4(int* A,int* B,long* C,int N,int NT);
void gemm_5(int* A,int* B,long* C,int N,int NT);
void gemm_6(int* A,int* B,long* C,int N,int NT);
void gemm_7(int* A,int* B,long* C,int N,int NT);
void gemm_8(int* A,int* B,long* C,int N,int NT);
void main(int argc,char** argv)
{
if(argc!=4 && argc!=3)
{
printf("shoule param: ./exe [(int)para1:select func] [(int)para2: dim] [(int)para3 thread_nums]\n");
return;
}
int func=1;
int N=12;
int NT=1;
if(argc==3)
{
func = atoi(argv[1]);
N = atoi(argv[2]);
}
else if(argc==4)
{
func = atoi(argv[1]);
N = atoi(argv[2]);
NT = atoi(argv[3]);
} int r,c,k;
long sum=0;
int* A = calloc(N*N,sizeof(int));
int* B = calloc(N*N,sizeof(int));
long* C = calloc(N*N,sizeof(long));
// 初始化
for(int r=0;r<N;r++)
{
for(int c=0;c<N;c++)
A[r*N+c] = B[r*N+c] = r+c;
} switch (func)
{
case 1:
NT = 1;
gemm_1(A,B,C,N,NT);
break;
case 2:
gemm_2(A,B,C,N,NT);
break;
case 3:
gemm_3(A,B,C,N,NT);
break;
case 4:
NT = 1;
gemm_4(A,B,C,N,NT);
break;
case 5: //忽略,无法 collapse(5)。计算错误
// gemm_5(A,B,C,N,NT);
break;
case 6:
NT = 1;
gemm_6(A,B,C,N,NT);
break;
case 7:
gemm_7(A,B,C,N,NT);
break;
case 8:
gemm_8(A,B,C,N,NT);
break;
default:
break;
} free(A);
free(B);
free(C);
} long sum(long* C,int n)
{
long sum=0;
for(int i=0;i<n*n;i++)
sum+=C[i];
return sum;
} void print_matrix(long* matrix, int rows, int cols)
{
printf("matrix:\n");
for (size_t i = 0; i < rows; i++)
{
for (size_t j = 0; j < cols; j++)
printf("%ld\t",matrix[i*cols+j]);
printf("\n");
}
} void print_matrixI(int* matrix, int rows, int cols)
{
printf("matrix:\n");
for (size_t i = 0; i < rows; i++)
{
for (size_t j = 0; j < cols; j++)
printf("%d\t",matrix[i*cols+j]);
printf("\n");
}
}

(1) 矩阵不分块 串行

void gemm_1(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
int r,c,k;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
for(r=0;r<N;r++)//A 行遍历
{
for(c=0;c<N;c++)//B 列遍历
{
// for循环 变量规约
long sum=0;
for(k=0;k<N;k++)//A B K方向遍历
sum += A[r*N+k] * B[k*N+c];
C[r*N+c] = sum;
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrix(C,N,N);
}

(2) 矩阵不分块 并行 collapse(2)

void gemm_2(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
omp_set_num_threads(NT);
// 使用collapse(3) C[r][c] 的不同数据再不同进程之间,会造成数据竞争,需要原子操作,造成速度过慢。
// 使用collapse(3) for 默认循环指标变量私有,需要private(k)
#pragma omp parallel for collapse(2) schedule(guided) proc_bind(close)
for(int r=0;r<N;r++)//A 行遍历
{
for(int c=0;c<N;c++)//B 列遍历
{
#if 0 //临界区+collapse(3)
for(int k=0;k<N;k++)//A B K方向遍历
{
#pragma omp critical
C[r*N+c] += A[r*N+k] * B[k*N+c];
}
#else
long sum=0; //使用局部变量减少传入参数引用
for(int k=0;k<N;k++)//A B K方向遍历
sum += A[r*N+k] * B[k*N+c];
C[r*N+c] = sum;
#endif
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrixI(A,N,N);
// printf("-----\n");
// print_matrix(C,N,N); }

(3) 矩阵不分块 并行 collapse(2)+ reduction

void gemm_3(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
int r,c,k;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
omp_set_num_threads(NT);
#pragma omp parallel for collapse(2) schedule(guided) proc_bind(close)
for(r=0;r<N;r++)//A 行遍历
{
for(c=0;c<N;c++)//B 列遍历
{
// for循环 变量规约 默认k私有
long sum=0;
#pragma omp parallel for reduction(+:sum) schedule(guided) proc_bind(close)
for(k=0;k<N;k++)//A B K方向遍历
sum += A[r*N+k] * B[k*N+c];
C[r*N+c] = sum;
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrix(C,N,N);
}

(4) 矩阵一维行列分块 A块 4N,B块 N4,串行,K在外层

void gemm_4(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
int r,c,k;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
for(r=0;r<N;r+=4)//A 行遍历
{
for(c=0;c<N;c+=4)//B 列遍历
{
for(int k=0;k<N;k++) //K方向遍历
{
for(int nr=0;nr<4;nr++) //A块内行遍历
{
for(int nc=0;nc<4;nc++) //B快内列遍历
C[(r+nr)*N+c+nc] += A[(r+nr)*N+k] * B[k*N+c+nc];
}
}
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrix(C,N,N);
}

(5) 矩阵一维行列分块 A块 4N,B块 N4,并行,K在外层,collapse。2层外 3层内循环,无法 collapse(5)。

void gemm_5(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
int r,c,k;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
omp_set_num_threads(NT);
#pragma omp parallel for collapse(5) schedule(guided) proc_bind(close)
for(r=0;r<N;r+=4)//A 行遍历
{
for(c=0;c<N;c+=4)//B 列遍历
{
for(int k=0;k<N;k++) //K方向遍历
{
for(int nr=0;nr<4;nr++) //A块内行遍历
{
for(int nc=0;nc<4;nc++) //B快内列遍历
{
C[(r+nr)*N+c+nc] += A[(r+nr)*N+k] * B[k*N+c+nc];
}
}
}
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrix(C,N,N);
}

(6) 矩阵一维行列分块 A块 4N,B块 N4,串行,K在内层。

void gemm_6(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
int r,c,k;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
for(r=0;r<N;r+=4)//A 行遍历
{
for(c=0;c<N;c+=4)//B 列遍历
{
for(int nr=0;nr<4;nr++) //A块内行遍历
{
for(int nc=0;nc<4;nc++) //B快内列遍历
{
long sum =0;
for(int k=0;k<N;k++) //K方向遍历
sum += A[(r+nr)*N+k] * B[k*N+c+nc];
C[(r+nr)*N+c+nc] = sum;
}
}
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrix(C,N,N);
}

(7) 矩阵一维行列分块 A块 4N,B块 N4,并行,K在内层,collapse(2 + 3)

void gemm_7(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
int r,c,k;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
omp_set_num_threads(NT);
#pragma omp parallel for collapse(2) schedule(guided) proc_bind(close)
for(r=0;r<N;r+=4)//A 行遍历
{
for(c=0;c<N;c+=4)//B 列遍历
{
#pragma omp parallel for collapse(3) schedule(guided) proc_bind(close)
for(int nr=0;nr<4;nr++) //A块内行遍历
{
for(int nc=0;nc<4;nc++) //B快内列遍历
{
for(int k=0;k<N;k++) //K方向遍历
C[(r+nr)*N+c+nc] += A[(r+nr)*N+k] * B[k*N+c+nc];
}
}
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrix(C,N,N);
}

(8) 矩阵一维行列分块 A块 4N,B块 N4,并行,K在内层,collapse (2+2)+ reduction

void gemm_8(int* A,int* B,long* C,int N,int NT)
{
struct timeval start,end;
float time;
int r,c,k;
//串行计算代码
gettimeofday(&start,NULL); //开始时间
omp_set_num_threads(NT);
#pragma omp parallel for collapse(2) private(r,c) schedule(guided) proc_bind(close)
for(r=0;r<N;r+=4)//A 行遍历
{
for(c=0;c<N;c+=4)//B 列遍历
{
#pragma omp parallel for collapse(2) schedule(guided) proc_bind(close)
for(int nr=0;nr<4;nr++) //A块内行遍历
{
for(int nc=0;nc<4;nc++) //B快内列遍历
{
long sum =0;
#pragma omp parallel for reduction(+:sum) schedule(guided) proc_bind(close)
for(int k=0;k<N;k++) //K方向遍历
sum += A[(r+nr)*N+k] * B[k*N+c+nc];
C[(r+nr)*N+c+nc] = sum;
}
}
}
}
gettimeofday(&end,NULL);
time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6;
printf("func %s N %d threads_num %d sum %ld useSec %f\n",__func__,N,NT,sum(C,N),time);
// print_matrix(C,N,N);
}

4. 测试数据

串行耗时(秒) FIELD2 FIELD3 FIELD4 FIELD5
函数名 线程数\维度 128 512 1024
gemm_1 矩阵不分块 串行 1 0.01786 1.52600 47.10980
gemm_4 矩阵一维行列分块 A块 4N,B块 N4,串行,K在外层 1 0.03303 2.13628 22.47993
gemm-6 矩阵一维行列分块 A块 4N,B块 N4,串行,K在内层, 1 0.02078 1.64609 65.69423
并行耗时(秒)
函数名 线程数\维度 128 512 1024
gemm_2 矩阵不分块 并行 collapse(2) 4 0.00515 0.36207 13.16278
8 0.00303 0.18024 6.34872
16 0.00382 0.12891 3.39717
32 0.00471 0.10166 2.02269
gemm_3 矩阵不分块 并行 collapse(2) + reduction 4 0.01050 0.54038 11.69852
8 0.00593 0.23172 6.51897
16 0.00807 0.16333 3.60325
32 0.01048 0.12297 2.12352
gemm_7 矩阵一维行列分块 A块 4N,B块 N4,并行,K在内层,collapse(2+3) 4 0.00893 0.71235 18.41659
8 0.00509 0.33909 8.78864
16 0.00803 0.23421 4.56388
32 0.01073 0.15445 2.65277
gemm_8 矩阵一维行列分块 A块 4N,B块 N4,并行,K在内层,collapse(2+2) + reduction 4 0.01120 0.58372 16.70111
8 0.00622 0.26817 7.68263
16 0.00756 0.14903 4.24847
32 0.01052 0.13294 2.71985

5. 结果分析

(1). 关于线程数:较小的数据规模应用较低的线程数,否则线程创建销毁影响效率。较大的数据规模应采用更大的线程数量。总之数据规模应该与线程数量匹配才能效率较大。

(2). 关于reduction:效率没有开启向量化优化 O1及以上有效(测试了1024维度4/32线程加速明显),在问题规模较小(512维度及以下)即使没有采用向量化优化也比 reduction 效率高。

(3). 关于 collapse 和 reduction:测试了 collapse(2+3) 和 collapse(2+2) + reduction,在数据规模较小及线程数少(128维度和8线程及以下)全collapse效率高,除此之外随着数据规模和线程数增加,collapse 的任务规模数量级增加,效率不如 reduction。

高性能计算-gemm-openmp效率测试(10)的更多相关文章

  1. 提升你的开发效率,10 个 NPM 使用技巧

    对于一个项目,常用的一些npm简单命令包含的功能有:初始化一个文件夹(npm init),下载npm模块(npm install),创建测试(npm test) 和自定义脚本(npm run).但是, ...

  2. 关于 pgsql 数据库json几个函数用法的效率测试

    关于 pgsql 数据库json几个函数用法的效率测试 关于pgsql 几个操作符的效率测试比较1. json::->> 和 ->> 测试方法:单次运行100次,运行10个单次 ...

  3. Python_线程、线程效率测试、数据隔离测试、主线程和子线程

    0.进程中的概念 三状态:就绪.运行.阻塞 就绪(Ready):当进程已分配到除CPU以外的所有必要资源,只要获得处理机便可立即执行,这时的进程状态成为就绪状态. 执行/运行(Running)状态:当 ...

  4. 进程池原理及效率测试Pool

    为什么会有进程池的概念? 当我们开启50个进程让他们都将100这个数减1次减到50,你会发现特别慢! 效率问题,原因: 1,开辟内存空间.因为每开启一个进程,都会开启一个属于这个进程池的内存空间,因为 ...

  5. postgresql-int,bigint,numeric效率测试

    在postgresql9.5的时候做过一个测试就是sum()的效率最终的测试结果是sum(int)>sum(numeric)>sum(bigint)当时比较诧异为啥sum(bigint)效 ...

  6. ORM for Net主流框架汇总与效率测试

    框架已经被越来越多的人所关注与使用了,今天我们就来研究一下net方面的几个主流ORM框架,以及它们的效率测试(可能会有遗漏欢迎大家讨论). ORM框架:Object/Relation Mapping( ...

  7. 纯PHP Codeigniter(CI) ThinkPHP效率测试

    最近一直想做一个技术类的新闻站点,想做的执行效率高些,想用PHP做,一直纠结于用纯PHP做还是用CI或者THINKPHP.用纯PHP效率高,缺点 n多,比如安全方面.构架方面等等等等:用CI.thin ...

  8. Python--day39--进程池原理及效率测试

    #为什么要有进程池的概念 #效率 #每次开启进程都要创建一个属于这个进程的内存空间 #寄存器 堆栈 文件 #进程过多 操作系统调度进程 # #进程池 #python中的 先创建一个属于进程的池子 #这 ...

  9. 关于pgsql 几个操作符的效率测试比较

    关于pgsql 几个操作符的效率测试比较1. json::->> 和 ->> 测试方法:单次运行100次,运行10个单次取平均时间.测试结果:->> 效率高 5% ...

  10. NHibernate Demo 和 效率测试

    本文关于NHibernate的Demo和效率测试,希望对大家有用. 1.先去官网下载Nhibernate 2.放入到项目中并建立Helper类 private static ISession _Ses ...

随机推荐

  1. 【Python爬虫】之爬取页面内容、图片以及用selenium爬取

    下面不做过多文字描述: 首先.安装必要的库 # 安装BeautifulSoup pip install beautifulsoup4 # 安装requests pip install requests ...

  2. Python 项目及依赖管理工具技术选型

    Python 项目及依赖管理工具,类似于 Java 中的 Maven 与 Node 中的 npm + webpack,在开发和维护项目时起着重要的作用.使用适当的依赖管理工具可以显著提高开发效率,减少 ...

  3. el-popover - 问题

    背景:elemet - ui和vue , el-table中使用了 el-popover , el-popover 中使用了form, 每编辑一行数据,点击编辑按钮,出现el-popover弹窗,页面 ...

  4. ASP.NET Core – Try Preview

    前言 .NET 7 已经来到 RC 阶段了. 通常 RC 就是我们 (写库的人) 要入场的时候了. 有发现 Bug 要尽可能在这段期间提交. 不然后患无穷. 这篇主要就是来讲讲如果测试 RC 版本的 ...

  5. Maven高级——分模块开发与设计

    分模块开发的意义 将原始模块按照功能拆分成若干个子模块,方便模块间的相互调用,接口共享 分模块开发 创建Maven工程 书写模块代码 注意:分模块开发需要先针对模块功能进行设计,再进行编码.不会先将工 ...

  6. QT6 QML编程

    QT6 QML编程 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C++扩展开发视频课程 免费QT视频课程 您可以看免费1000+个QT技 ...

  7. 个人Blog的第一篇博文

    个人Blog的第一篇博文 正式加入"博客园"大家庭了,希望以后可以一直坚持下去欸.

  8. ArgoWorkflow教程(五)---Workflow 的多种触发模式:手动、定时任务与事件触发

    上一篇我们分析了argo-workflow 中的 archive,包括 流水线GC.流水线归档.日志归档等功能.本篇主要分析 Workflow 中的几种触发方式,包括手动触发.定时触发.Event 事 ...

  9. 生成系统中的maven依赖信息

    在项目终端直接执行命令 mvn project-info-reports:dependencies 等待文件生成... 生成信息如下...

  10. pytorch中y.data.norm()的含义

    import torch x = torch.randn(3, requires_grad=True) y = x*2 print(y.data.norm()) print(torch.sqrt(to ...