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. 基于PaddleOCR + NLP实现证件识别

    基于PaddleOCR + NLP实现证件识别 PaddleOCR识别 paddleOCR安装 安装 anconda虚拟环境(可参考yolov5的安装教程) paddleOCR识别 PaddleNLP ...

  2. 新手入门 | 搭建 AI 模型开发环境

    目录 安装显卡驱动和开发库 对于 Tesla 系列显卡 对于 N 卡 安装 CUDA 和 cuDNN 安装 Miniconda 安装 PyTorch 和 Transformers 使用 Modelsc ...

  3. 事务TRANSACTION 1

    MySQL事务是指一组SQL语句作为一个工作单元,同生共死.类似JS中的Array.prototype.every方法 事务的特点 原子性(Atomicity):即事务中的SQL像原子一样牢不可破,无 ...

  4. 宝塔安装onlyoffice

    1. 拉取镜像 docker pull onlyoffice/documentserver 2. 构建容器 进入宝塔管理面板->docker->镜像,可以看到镜像已被安装成功 配置容器参数 ...

  5. C#/.NET/.NET Core优秀项目和框架2024年8月简报

    前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍.功能特点.使用方式以及部分功能截图 ...

  6. 知识点考古:php5的面向对象学习笔记

    闲来无事翻看以前收藏的资料,考古到保存的这篇文章对php的OOP的整理还很系统.原链接已经打不开(http://www.cublog.cn/u/17686/showart.php?id=146562) ...

  7. Angular Material 18+ 高级教程 – CDK Drag and Drop

    前言 CDK Drag and Drop 和 CDK Scrolling 都是在 Angular Material v7 中推出的. 它们有一个巧妙的共同点,那就是与 Material Design ...

  8. CSS – 实战 Color

    前言 之前 W3Schools 学习笔记 (1) 也记入过 Color, 这篇整理一下在网页开发中, 颜色是如果被处理的. 网页都有什么颜色? 网页篇幅最大的颜色, 通常是来自图片, 不管是背景图, ...

  9. 揭秘最为知名的黑客工具之一: Netcat!适用安全测试、渗透测试、黑客攻击!

    在网络安全领域,黑客工具一直备受关注.它们既可以被用于攻击,也可以用于防御.本文将为大家揭秘一款知名的黑客工具: Netcat. 1.Netcat是什么? Netcat被誉为"网络的瑞士军刀 ...

  10. QT框架中的缓存:为什么有QHash和QMap,还设计了QCache和QContiguousCache?

    简介 本文介绍了QT框架中可用于缓存的几个数据类型各自的特点:通过本文读者可以了解到为什么有QHash和QMap,还设计了QCache和QContiguousCache? 目录 QHash和QMap有 ...