C++性能优化系列——矩阵转置(四)OpenMP并行计算

2023-11-16

本系列之前的篇章都是基于单线程处理。实际工程中,通过多线程对程序进行并行化往往是最简单且直接有效的优化手段。本篇以C++性能优化系列——矩阵转置(三)内存填充避免缓存抖动 中优化好的程序为Base版本,通过OpenMP技术,对程序进一步做并行化加速。同时,对OpenMP提供的并行化与开辟并行区方式进行试验,探索出OpenMP做并行化的一些规律。

c++转置并行化

代码实现

unsigned char* pSource;
		pSource = (unsigned char*)malloc(sizeof(unsigned char) * NREALCOL * NROW);
		for (int irow = 0; irow < NROW; ++irow)
		{
			memset(pSource + irow * NREALCOL, irow % 256, sizeof(unsigned char) * NREALCOL);//按照字节赋值
		}
		unsigned char* pTarget;
		InitMem(pTarget);
		clock_t begin = clock();
		int nbC = NCOL / BLOCK, nbR = NROW / BLOCK;
		for (int i = 0; i < REPEAT; ++i)
		{
#pragma omp parallel for num_threads(8) schedule(static)
			for (int ibr = 0; ibr < nbR; ++ibr)
			{
				for (int ibc = 0; ibc < nbC; ++ibc)
				{
					for (int irow = 0; irow < BLOCK; ++irow)
					{
						for (int icol = 0; icol < BLOCK; ++icol)
						{
							pTarget[(ibr * BLOCK + irow) * NROW + icol] = pSource[(ibc * BLOCK + icol) * NREALCOL + irow];
						}
					}
				}
			}

		}
		clock_t end = clock();
		std::cout << "PaddingTranspose 1024 Time " << (end - begin) << std::endl;
		std::cout << "PaddingTranspose parallel Time (ms) " << ((float)(end - begin)) / (float)REPEAT << std::endl;

代码中for循环使用静态的策略。
执行时间

PaddingTranspose parallel Time (ms) 0.114258

同时,也尝试了对for循环使用不同的调度模式。
运行时动态调度

#pragma omp parallel for num_threads(8) schedule(dynamic)

执行时间

PaddingTranspose parallel Time (ms) 0.105469

默认调度

#pragma omp parallel for num_threads(8)

执行时间

PaddingTranspose parallel Time (ms) 0.121094

三种并行化方式,对比Base版本,得到的最大加速比为 0.367188 / 0.105469 = 3.48。加速比并不理想。
测试执行耗时排序:
动态调度 < 静态调度 < 默认调度
静态调度方法,理论上每个线程的负载是一样的,原则上应该是static的方式运行速度应该最快。基于我的认知,尝试解释当前执行速度不符合预期的原因:实际程序运行情况与理论存在一定的偏差,由于矩阵尺寸太小,每个线程的负载太低,同时线程启动,同步等耗时不均匀,并行区线程启动等耗时占用比例过大,造成了目前这样的现象。

VTune分析

函数耗时统计,可以看到OpenMP线程同步占一定的比重。
在这里插入图片描述

并行区线程执行情况。
在这里插入图片描述
在这里插入图片描述
并行区内8个线程在有效执行指令中,夹杂着大量同步等待时间。
造成这一点的可能原因(个人理解):
并行化过程包括创建并行区,各线程执行分配来的任务,并行区析构。每个过程的执行都要消耗一定的CPU执行时间。同时,一次并行执行的过程中,每个线程的只是对128 * 1024个Byte数据进行内存搬运,线程负载太小。因此,当前的实现方式(频繁的创建和析构并行区)造成多线程利用率低,多线程加速比不理想。
此外,尝试了在for循环最外层开辟并行区,内层循环用omp for迭代,避免并行区的频繁开辟,效果不明显,执行时间几乎没变化。

试验OpemMP并行方案

针对通过OpenMP对for循环的并行处理,可引申出两个问题:
1.OpenMP的调度方式static/dynamic对执行性能的影响
2.开辟并行区位置对执行性能的影响

为了测试上述影响,只需要对两个因素分别做更改并依次试验:并行区位置与调度方式。此外,为了排除线程负载过小带来的干扰,加大线程的负载,将二维矩阵尺寸更改为 32k * 32k。一次迭代每个线程要处理数据128M。

矩阵尺寸相关定义,其中把矩阵尺寸更新成32K,同时为了避免运行时间过长,通过调成重复次数至50次。

#define NROW 1024*32
#define NCOL 1024*32
#define NREALCOL (NCOL + 128)
#define NSLICE NROW*NCOL
#define REPEAT 50
#define BLOCK 128

内部开辟并行区,静态调度

代码实现

		for (int i = 0; i < REPEAT; ++i)
		{
#pragma omp parallel for num_threads(8) schedule(static)
			for (int ibr = 0; ibr < nbR; ++ibr)
			{
				for (int ibc = 0; ibc < nbC; ++ibc)
				{
					for (int irow = 0; irow < BLOCK; ++irow)
					{
						for (int icol = 0; icol < BLOCK; ++icol)
						{
							pTarget[(ibr * BLOCK + irow) * NROW + icol] = pSource[(ibc * BLOCK + icol) * NREALCOL + irow];
						}
					}
				}
			}

		}

执行时间

PaddingTranspose parallel Time (ms) 120.84

VTune数据
在这里插入图片描述
在这里插入图片描述
可以看到,代码执行时,并行区析构前的同步占了一定的比重,影响了程序执行的性能。

代码内部开辟并行区,动态调度

代码实现

for (int i = 0; i < REPEAT; ++i)
		{
//#pragma omp for schedule(dynamic)
#pragma omp parallel for num_threads(8) schedule(dynamic)
			for (int ibr = 0; ibr < nbR; ++ibr)
			{
				for (int ibc = 0; ibc < nbC; ++ibc)
				{
					for (int irow = 0; irow < BLOCK; ++irow)
					{
						for (int icol = 0; icol < BLOCK; ++icol)
						{
							pTarget[(ibr * BLOCK + irow) * NROW + icol] = pSource[(ibc * BLOCK + icol) * NREALCOL + irow];
						}
					}
				}
			}

		}

执行时间

PaddingTranspose parallel Time (ms) 127.04

VTune数据
在这里插入图片描述
在这里插入图片描述
可以看到,动态调度CPU利用效率更高,并行区内部线程同步时间比重降低了,占用的CPU资源基本上都用在执行程序。从有效时间比重这个参数来说,程序时变好了。但是执行速度变慢了,猜测可能的原因:VTune将并行区创建和析构统计为有效的执行时间,同时动态调度策略会对运行时的一些情况作出判断,额外的动作会增加线程负载。因此虽然比重降低了,但是总时间增加。

外部开辟并行区,内部静态调度

代码实现

#pragma omp parallel num_threads(8) 
		for (int i = 0; i < REPEAT; ++i)
		{
#pragma omp for schedule(static)
//#pragma omp parallel for num_threads(8) schedule(dynamic)
			for (int ibr = 0; ibr < nbR; ++ibr)
			{
				for (int ibc = 0; ibc < nbC; ++ibc)
				{
					for (int irow = 0; irow < BLOCK; ++irow)
					{
						for (int icol = 0; icol < BLOCK; ++icol)
						{
							pTarget[(ibr * BLOCK + irow) * NROW + icol] = pSource[(ibc * BLOCK + icol) * NREALCOL + irow];
						}
					}
				}
			}

		}

执行时间

PaddingTranspose parallel Time (ms) 115.38

VTune数据
在这里插入图片描述
在这里插入图片描述
虽然夹杂的大量的同步时间,但是这种方法比内部开辟并行区执行速度快了一些。

外部开辟并行区,内部动态调度

代码实现

#pragma omp parallel num_threads(8) 
		for (int i = 0; i < REPEAT; ++i)
		{
#pragma omp for schedule(dynamic)
//#pragma omp parallel for num_threads(8) schedule(dynamic)
			for (int ibr = 0; ibr < nbR; ++ibr)
			{
				for (int ibc = 0; ibc < nbC; ++ibc)
				{
					for (int irow = 0; irow < BLOCK; ++irow)
					{
						for (int icol = 0; icol < BLOCK; ++icol)
						{
							pTarget[(ibr * BLOCK + irow) * NROW + icol] = pSource[(ibc * BLOCK + icol) * NREALCOL + irow];
						}
					}
				}
			}

		}

执行时间

PaddingTranspose parallel Time (ms) 103.86

VTune数据
在这里插入图片描述
在这里插入图片描述
同步时间少,运行速度最快。

四组测试对比下来,外部开辟并行区,内部for循环动态调度 方案运行最快。
对比四个测试程序的执行情况,可以得到以下规律:
1.静态调度相比动态调度,线程同步的时间占比更大,但是总时间哪个方法快不能确定。
2.并行区开辟在最外层(即避免频繁的创建和析构并行区)速度更快,额外开销更小。

总结

本篇对矩阵转置进行并行化处理,在8核心的CPU上,实际得到的加速比为3.48,并不理想。同时,引申出的OpenMP的使用方法的探讨,文中的测试程序因为任务粒度小,因此动态调度效果好于静态调度。对于实际更加复杂的工程代码,可以通过尝试不同的调度方案来最终确定合理的调度方式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++性能优化系列——矩阵转置(四)OpenMP并行计算 的相关文章

  • 在 C# 中使用“using”关键字避免多次处置的最佳实践

    当变量是 IDisposable 时 我们有using关键字来管理处置 但是如果我们在方法中返回值怎么办 using twice StringContent stringToStringContent string str using St
  • 元组在 VS2012 中如何工作?

    Visual Studio 2012 功能 tuples但不是可变参数模板 这是如何完成的 如何在不使用可变模板的情况下实现元组 简而言之 微软做了与之前在 NET 中实现类似元组的数据类型完全相同的事情 创建许多版本 每个版本都有固定数量
  • c# 从另一个类中的另一个静态事件引发事件

    需要帮助从另一个班级调用事件 我有已声明事件的课程 public class MxPBaseGridView GridView public event AddNewItemsToPopUpMenuEventHandler AddNewIt
  • IEnumerable 的 String.Join(string, string[]) 的类似物

    class String包含非常有用的方法 String Join string string 它从数组创建一个字符串 用给定的符号分隔数组的每个元素 但一般来说 它不会在最后一个元素之后添加分隔符 我将它用于 ASP NET 编码 以用
  • 异常堆栈跟踪不显示抛出异常的位置

    通常 当我抛出异常 捕获它并打印出堆栈跟踪时 我会看到抛出异常的调用 导致该异常的调用 导致该异常的调用that 依此类推回到整个程序的根 现在它只向我显示异常所在的调用caught 而不是它所在的地方thrown 我不明白是什么改变导致了
  • 如何以编程方式播放 16 位 pcm 数组 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有一个包含 16 位 pcm 值的短 数组 我希望能够在不添加任何标题 也不将任何文件保存到内存的情况下播放它 我知道我可能需要一个提供
  • C 中“complex”的默认类型

    根据我读过的文档 C99 和更高版本的支持float complex double complex and long double complex作为复杂类型 但是 此代码在使用时编译时不会发出警告gcc Wall Wextra inclu
  • 全局使用和 .NET Standard 2.0

    我最近意识到我可以使用 C 10 功能文件范围的命名空间在 NET Standard 2 0 项目中也可以通过设置
  • 你好,我最近正在开发我的新游戏,我遇到了*无限跳跃*的问题

    所以基本上当我按跳跃 空格键时我会跳跃但是如果我连续按空格键它 只是跳啊跳啊跳等等 我不想要我只想它跳一次 code if Input GetKeyDown space isGrounded velocity y Mathf Sqrt ju
  • 用于连接 DataTable 上的动态列的动态 LINQ

    我目前遇到的情况不确定如何继续 我有两个从数据库填充的数据表 我还有一个可用的列名称列表 可用于将这两个数据表连接在一起 我希望编写一组 LINQ 查询 这些查询将 显示两个数据表中的行 内部联接 用于从一个数据表更新另一个数据表 显示一个
  • 如何使用递归查找数字中的最小元素 [C]

    好的 所以我正在准备我的 C 考试 当谈到递归时我有点卡住了我是大学一年级的学生 这对我来说似乎有点困难 练习要求在给定的数字中使用递归函数我需要找到最小的元素 例如 52873 是 2 程序需要打印 2 include
  • 无法为 wsdl 文件创建服务引用

    I have wsdl文件和xsd我本地机器上的文件 我想在项目中添加服务引用 我没有网络服务 我只有wsdl file 我收到以下错误 The document was understood but it could not be pro
  • doxygen c++:记录由“using”声明公开的私有继承成员

    作为一个例子 我有以下课程 class A public void methodOne class B private A public Brief description using A methodOne 我还没有找到强制 doxyge
  • 为什么 f(i = -1, i = -1) 是未定义的行为?

    我正在读关于违反评估顺序 http en cppreference com w cpp language eval order 他们举了一个令我困惑的例子 1 如果标量对象上的副作用相对于同一标量对象上的另一个副作用是无序的 则行为未定义
  • 使用 xslt 将 xml 转换为 xsl-fo 时动态创建超链接?

    我想使用 xsl 文件在 PDF 报告中创建标题 如果源文件包含超链接 则应将其呈现为超链接 否则呈现为纯文本 例如 我的 xml 如下所示 a href http google com target blank This is the h
  • 浮点字节序?

    我正在为实时海上模拟器编写客户端和服务器 并且由于我必须通过套接字发送大量数据 因此我使用二进制数据来最大化可以发送的数据量 我已经了解整数字节顺序以及如何使用htonl and ntohl为了规避字节顺序问题 但我的应用程序与几乎所有模拟
  • Autoconf 问题:“错误:C 编译器无法创建可执行文件”

    我正在尝试使用 GNU 自动工具构建一个用 C 编写的程序 但显然我设置错误 因为当configure运行 它吐出 configure error C compiler cannot create executables 如果我看进去con
  • printf或iostream如何指定点后的最大位数

    字符串采用什么格式printf or iomanip我应该使用 iostream 中的运算符以以下格式打印浮点数 125 0 gt 125 125 1 gt 125 1 125 12312 gt 125 12 1 12345 gt 1 12
  • 如何在c linux中收听特定接口上的广播?

    我目前可以通过执行以下操作来收听我编写的简单广播服务器 仅广播 hello int fd socket PF INET SOCK DGRAM 0 struct sockaddr in addr memset addr 0 sizeof ad
  • 如何提高环复杂度?

    对于具有大量决策语句 包括 if while for 语句 的方法 循环复杂度会很高 那么我们该如何改进呢 我正在处理一个大项目 我应该减少 CC gt 10 的方法的 CC 并且有很多方法都存在这个问题 下面我将列出一些例如我遇到的问题的

随机推荐

  • H.264概述

    我的百科 我的贡献 草稿箱 百度首页 登录 新闻 网页 贴吧 知道 MP3 图片 视频 百科 帮助设置 添加到搜藏 返回百度百科首页 编辑词条 H 264
  • STM32控制L298n(从零开始)

    一 L298N模块简介 L298N是一款驱动模块 单片机通过向IN1 IN2 IN3 IN4输入PWM波从而控制OUT1 OUT2 ENA与ENB为使能引脚 使能引脚两根排针一定要短接 12v为模块供电 5v为单片机供电 二 L298N的逻
  • 什么是CentOS

    什么是CentOS CentOS是Community ENTerprise Operating System的简称 我们有很多人叫它社区企业操作系统 不管你怎么叫它 它都是linux的一个发行版本 CentOS并不是全新的linux发行版
  • MySQL密码忘记了怎么办?

    MySQL密码忘记了怎么办 本文就介绍了如何用canvas案例画出哆啦A梦的基础内容 提示 以下是本篇文章正文内容 下面案例可供参考 一 1 打开cmd命令符 先关闭正在运行的数据库 输入如下命令 二 打开mysql exe和mysqld
  • VUE的核心特性:响应式

  • 【Pytorch Lighting】第 6 章:深度生成模型

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • openEuler和linux什么关系,华为openEuler和鸿蒙(HarmonyOS)不是同一个操作系统

    华为推出了新操作系统 定名为openEuler 当前已提供20 03版本下载 有人不解的问 它跟鸿蒙 HarmonyOS 是不是同一个操作系统 或者有什么关系 华为openEuler和鸿蒙 HarmonyOS 100 不是同一个操作系统 并
  • string类型数组java_Java string类和数组的相关函数总结

    一 string类 1 字符串查找 1 str indexOf substr 返回substr首次在str里出现的索引 str 任意字符串对象 substr 要搜索的字符串 2 str lastIndexOf substr 返回substr
  • 洛谷P1011 [NOIP1998 提高组] 车站题解

    斐波那契数列 题目描述 火车从始发站 称为第1站 开出 在始发站上车的人数为a 然后到达第2站 在第2站有人上 下车 但上 下车的人数相同 因此在第2站开出时 即在到达第3站之前 车上的人数保持为a人 从第3站起 包括第3站 上 下车的人数
  • 拒绝摆烂!C语言练习打卡第四天

    博客主页 小王又困了 系列专栏 每日一练 人之为学 不日近则日退 感谢大家点赞 收藏 评论 目录 一 选择题 1 第一题 2 第二题 3 第三题 二 编程题 1 第一题 2 第二题 前言 在前面我们学习完C语言的所以知识 当然练习巩固也不能
  • 万维网(www)

    万维网 www 是互联网中使用最广泛的一种应用 是一个超大规模线上信息储藏所 以链接的形式为用户提供信息检索服务 万维网的核心作用 使得信息在网上自由传输 万维网提供分布式的信息检索服务 箭头代表以链接的形式跳转到其他页面 这种方式使得一个
  • 【Maven】单元测试、统计、覆盖率相关插件使用介绍

    maven surefire plugin maven surefire plugin是maven执行单元测试的插件 不显性配置也可以直接使用 这个插件的surefire test命令会默认绑定maven执行的test阶段 执行结束后 默认
  • Mysql数据库的环境搭建【详细】

    作者简介 大学机械本科 野生程序猿 学过C语言 玩过前端 还鼓捣过嵌入式 设计也会一点点 不过如今痴迷于网络爬虫 因此现深耕Python 数据库 seienium JS逆向 安卓逆向等等 目前为全职爬虫工程师 学习的过程喜欢记录 目前已经写
  • ASM插桩:学完ASM Tree api,再也不用怕hook了

    背景 对于ASM插桩来说 可能很多人都不陌生了 但是大多数可能都停留在core api上 对于现在市面上的一些插桩库 其实很多都用tree api进行编写了 因为tree api的简单与明了的特性 也越来越成为许多开源库的选择 ASM有两套
  • 【STM32CubeMX】位置式PID调节控制输出电压(超详解)

    本文将借助STM32CubeMX来配置ADC DMA DAC USART 并利用PID位置式算法实现对输出电压进行AD采集通过PID算法调节DAC 获取到我们想要的电压值 讲解的主要知识 何为PID以及为何需要PID STM32CubeMX
  • 计算机含金量最高的证书

    第一种证书 计算机技术与软件专业资格考试证书 计算机技术与软件专业资格考试证书 是由国家人力资源和社会保障部 工业和信息化部领导的国家级考试 该考试分为 5 个专业类别 并分设了高 中 初级专业资格考试 共 28 个资格的考核 也是用人单位
  • Open3D(C++) 根据索引提取点云

    目录 一 功能概述 1 主要函数 2 源码 二 代码实现 三 结果展示 本文由CSDN点云侠原创 原文链接 爬虫网站自重 把自己当个人 一 功能概述 1 主要函数 std shared ptr
  • 添加CSS样式的三种方法与CSS的注释

    目录 三种使用 CSS 的方法 外部 CSS 实例 mystyle css 内部 CSS 实例 行内 CSS 实例 多个样式表 实例 实例 层叠顺序 CSS 注释 实例 实例 实例 HTML 和 CSS 注释 实例 当浏览器读到样式表时 它
  • R_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server;

    error Error R NOT SUPPORTED AUTH MODE Client does not support authentication protocol requested by server MySQL 8 has su
  • C++性能优化系列——矩阵转置(四)OpenMP并行计算

    本系列之前的篇章都是基于单线程处理 实际工程中 通过多线程对程序进行并行化往往是最简单且直接有效的优化手段 本篇以C 性能优化系列 矩阵转置 三 内存填充避免缓存抖动 中优化好的程序为Base版本 通过OpenMP技术 对程序进一步做并行化