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. c++学习笔记(四):面向对象

    目录 类 & 对象 封装 访问权限 类的构造函数&析构函数 构造函数的分类及调用 拷贝构造函数的调用时机 构造函数调用规则 深拷贝与浅拷贝 初始化列表 类对象作为类成员 静态成员 C+ ...

  2. [Udemy] AWS Certified Data Analytics Specialty - 2.Storage

    S3 Replication (CRR & SRR) S3 Encryption S3 Security 其中两个ACL基本不会考 记住这3个event发送的target DynamoDB D ...

  3. 面试官:limit 100w,10为什么慢?如何优化?

    在 MySQL 中,limit X,Y 的查询中,X 值越大,那么查询速度也就越慢,例如以下示例: limit 0,10:查询时间大概在 20 毫秒左右. limit 1000000,10:查询时间可 ...

  4. Angular 18+ 高级教程 – Signals

    前言 首先,我必须先说明清楚.Signal 目前不是 Angular 的必备知识. 你的项目不使用 Signal 也不会少了条腿,断了胳膊. Angular 官方维护的 UI 组件库 Angular ...

  5. 人脸识别 face detect & recognize

    前言 最近有一个项目要升级. 它是一个在线教育的 web app. 由于学生年龄小, 不适合用 username/password 这种方式做登入. 所以项目开始之初是使用 RFID 来登入的. 但由 ...

  6. 开源项目dotnet/eshop 和 dotnet/eshopsupport

    dotnet/eshop[1] 和 dotnet/eshopsupport[2] 是两个与 .NET 相关的开源项目,分别用于展示电子商务应用的不同方面. dotnet/eshop: 功能与架构:do ...

  7. Hadoop & Redis未授权漏洞实战——Vulfocus服务攻防

    什么是未授权访问漏洞?Hadoop & Redis靶场实战--Vulfocus服务攻防 一.介绍 未授权访问,也称为未经授权的访问或非法访问,是指在没有得到适当权限或授权的情况下,个人或系统访 ...

  8. Scala编程语言基本常识

    ​ 本文参考来源: Scala编程语言基本常识 – 萌面人博客 ​ 一 基本常识 Scala被称为大数据的黄金语言,它的发明人是马丁奥德斯基(Martin Odersky) 2001年诞生,融合了Ja ...

  9. 加入 Flutter Engage,Pick 您的专属 Dash 形象!

    Flutter Engage 活动精彩来袭 对 Flutter 团队的开发者们来说,交流的重要性不言而喻,和您一样,我们也希望开发者们能够在不同的情境下进行互动分享.于是我们为您准备了一场特别的线上活 ...

  10. 队列之ring_buffer优雅实现--附个人代码理解

    1. 下面张贴实现该队列仅需的两个头文件:ring_buffer_iterator.h 和 fifo_iterator.h ring_buffer_iterator.h 1 /* 2 * 3 * Th ...