高性能计算-gemm-openmp效率测试(10)
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)的更多相关文章
- 提升你的开发效率,10 个 NPM 使用技巧
对于一个项目,常用的一些npm简单命令包含的功能有:初始化一个文件夹(npm init),下载npm模块(npm install),创建测试(npm test) 和自定义脚本(npm run).但是, ...
- 关于 pgsql 数据库json几个函数用法的效率测试
关于 pgsql 数据库json几个函数用法的效率测试 关于pgsql 几个操作符的效率测试比较1. json::->> 和 ->> 测试方法:单次运行100次,运行10个单次 ...
- Python_线程、线程效率测试、数据隔离测试、主线程和子线程
0.进程中的概念 三状态:就绪.运行.阻塞 就绪(Ready):当进程已分配到除CPU以外的所有必要资源,只要获得处理机便可立即执行,这时的进程状态成为就绪状态. 执行/运行(Running)状态:当 ...
- 进程池原理及效率测试Pool
为什么会有进程池的概念? 当我们开启50个进程让他们都将100这个数减1次减到50,你会发现特别慢! 效率问题,原因: 1,开辟内存空间.因为每开启一个进程,都会开启一个属于这个进程池的内存空间,因为 ...
- postgresql-int,bigint,numeric效率测试
在postgresql9.5的时候做过一个测试就是sum()的效率最终的测试结果是sum(int)>sum(numeric)>sum(bigint)当时比较诧异为啥sum(bigint)效 ...
- ORM for Net主流框架汇总与效率测试
框架已经被越来越多的人所关注与使用了,今天我们就来研究一下net方面的几个主流ORM框架,以及它们的效率测试(可能会有遗漏欢迎大家讨论). ORM框架:Object/Relation Mapping( ...
- 纯PHP Codeigniter(CI) ThinkPHP效率测试
最近一直想做一个技术类的新闻站点,想做的执行效率高些,想用PHP做,一直纠结于用纯PHP做还是用CI或者THINKPHP.用纯PHP效率高,缺点 n多,比如安全方面.构架方面等等等等:用CI.thin ...
- Python--day39--进程池原理及效率测试
#为什么要有进程池的概念 #效率 #每次开启进程都要创建一个属于这个进程的内存空间 #寄存器 堆栈 文件 #进程过多 操作系统调度进程 # #进程池 #python中的 先创建一个属于进程的池子 #这 ...
- 关于pgsql 几个操作符的效率测试比较
关于pgsql 几个操作符的效率测试比较1. json::->> 和 ->> 测试方法:单次运行100次,运行10个单次取平均时间.测试结果:->> 效率高 5% ...
- NHibernate Demo 和 效率测试
本文关于NHibernate的Demo和效率测试,希望对大家有用. 1.先去官网下载Nhibernate 2.放入到项目中并建立Helper类 private static ISession _Ses ...
随机推荐
- 【Python爬虫】之爬取页面内容、图片以及用selenium爬取
下面不做过多文字描述: 首先.安装必要的库 # 安装BeautifulSoup pip install beautifulsoup4 # 安装requests pip install requests ...
- Python 项目及依赖管理工具技术选型
Python 项目及依赖管理工具,类似于 Java 中的 Maven 与 Node 中的 npm + webpack,在开发和维护项目时起着重要的作用.使用适当的依赖管理工具可以显著提高开发效率,减少 ...
- el-popover - 问题
背景:elemet - ui和vue , el-table中使用了 el-popover , el-popover 中使用了form, 每编辑一行数据,点击编辑按钮,出现el-popover弹窗,页面 ...
- ASP.NET Core – Try Preview
前言 .NET 7 已经来到 RC 阶段了. 通常 RC 就是我们 (写库的人) 要入场的时候了. 有发现 Bug 要尽可能在这段期间提交. 不然后患无穷. 这篇主要就是来讲讲如果测试 RC 版本的 ...
- Maven高级——分模块开发与设计
分模块开发的意义 将原始模块按照功能拆分成若干个子模块,方便模块间的相互调用,接口共享 分模块开发 创建Maven工程 书写模块代码 注意:分模块开发需要先针对模块功能进行设计,再进行编码.不会先将工 ...
- QT6 QML编程
QT6 QML编程 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C++扩展开发视频课程 免费QT视频课程 您可以看免费1000+个QT技 ...
- 个人Blog的第一篇博文
个人Blog的第一篇博文 正式加入"博客园"大家庭了,希望以后可以一直坚持下去欸.
- ArgoWorkflow教程(五)---Workflow 的多种触发模式:手动、定时任务与事件触发
上一篇我们分析了argo-workflow 中的 archive,包括 流水线GC.流水线归档.日志归档等功能.本篇主要分析 Workflow 中的几种触发方式,包括手动触发.定时触发.Event 事 ...
- 生成系统中的maven依赖信息
在项目终端直接执行命令 mvn project-info-reports:dependencies 等待文件生成... 生成信息如下...
- pytorch中y.data.norm()的含义
import torch x = torch.randn(3, requires_grad=True) y = x*2 print(y.data.norm()) print(torch.sqrt(to ...