【OpenMP】 2.3 并行化循环

       

目录

1、for循环

2、规约

3、for循环中的调度(schedule API)

3.1 静态调度(static)

3.2 动态调度(dynamic)

调度的选择


1、for循环

         前面的示例中,通过创建一组线程并通过线程ID与线程数来人为的定义每个线程需要处理的数据,这是一个常规的多线程任务分配的技巧;但是在openmp中通过#pragma omp for指令,可以通过简单的并行化循环构造即可快速实现前述的任务分配环节。

         需要注意for构造指令需要在parallel构造的并行区域内才可以多线程运行。

#include <iostream>
#include <omp.h>
#include <vector>
//#define NTHREADS 4void task_manual() {std::vector<double> a{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};std::vector<double> b{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};size_t N = a.size();double start_time = omp_get_wtime();
#pragma omp parallel{//手动分配每个线程需要执行哪个任务int id, i, Nthrds, istart, iend;id = omp_get_thread_num();Nthrds = omp_get_num_threads();istart = id * N / Nthrds;iend = (id + 1) * N / Nthrds;if (id == Nthrds - 1) iend = N;for (i = istart; i < iend; i++) {a[i] = a[i] + b[i];}}std::cout << "cost time: " << omp_get_wtime() - start_time << "s" << std::endl;for (double &i: a) {std::cout << i << " ";}}void task_omp() {std::vector<double> a{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};std::vector<double> b{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};size_t N = a.size();double start_time = omp_get_wtime();
#pragma omp parallel default(shared){//通过omp for指令让线程组自动迭代处理//请注意循环中不要共享循环控制索引i,否则会导致线程间的数据竞争
#pragma omp forfor (int i = 0; i < N; i++) {a[i] = a[i] + b[i];}//此处存在一个隐式栅栏,所有线程都需要在这里等待与同步}std::cout << "cost time: " << omp_get_wtime() - start_time << "s" << std::endl;for (double &i: a) {std::cout << i << " ";}
}int main() {task_omp();
}
cost time: 0.00808858s
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 
Process finished with exit code 0

另外,可以将OMP构造指令组合在一起使用,如下示例所示:

void task_omp_simple() {std::vector<double> a{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};std::vector<double> b{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};size_t N = a.size();double start_time = omp_get_wtime();//合并写在一起
#pragma omp parallel for{for (int i = 0; i < N; i++) {a[i] = a[i] + b[i];}}std::cout << "cost time: " << omp_get_wtime() - start_time << "s" << std::endl;for (double &i: a) {std::cout << i << " ";}
}

2、规约

        规约可以通过多线程加速实现实现数组的累加、累乘、查找等工作。

openmp的规约通过指令reduction(op:list)实现,其中op定义了本次规约运算的操作符,如+、-、*、min、max、逻辑运算符、位运算符等。同时注意omp中的规约子句中一次只能指定一个运算符。

其中个运算符线程的私有变量初值如下:

运算符初始值
+0
-0
*1
min变量类型最大正数
max变量类型最大负数

#include <iostream>
#include <omp.h>
#include <vector>//串行规约的实现
void plain(std::vector<double> &a) {double ave = 0;double start_time = omp_get_wtime();for (double i: a) {ave += i;}ave = ave / a.size();std::cout << "result: " << ave << ",cost time: " << omp_get_wtime() - start_time << std::endl;
}//omp 规约的实现
void reduce(std::vector<double> &a) {double ave = 0;double start_time = omp_get_wtime();/*omp的规约实现中,将会为每个线程创建一个同名的ave变量的私有副本,并根据规约运算符完成私有副本的初始化,待每个线程完成各自部分的规约计算后,在结尾的隐式栅栏处完成变量的合并计算。*/
#pragma omp parallel for reduction(+:ave)for (double i: a) {ave += i;}//此处包含隐式栅栏ave = ave / a.size();std::cout << "result: " << ave << ",cost time: " << omp_get_wtime() - start_time << std::endl;
}int main() {std::vector<double> a;int vectorSize = 1e8;a.reserve(vectorSize);for (int i = 0; i < vectorSize; ++i) {a.emplace_back(1);}plain(a);reduce(a);
}

result: 1,cost time: 0.0952761
result: 1,cost time: 0.0465072Process finished with exit code 0

3、for循环中的调度(schedule API)

        在OpenMP显示API中包含两个常用schedule子句,分别为static、dynamic。使用方法如下:

schedule (static [,chunk]),chunk默认为1。

schedule (dynamic [,chunk]),chunk默认为1。

3.1 静态调度(static)

        静态调度在编译的过程中将会将共享工作循环迭代地映射到线程上。如果没有指定每一个分块chunk的大小,编译器会默认将循环迭代分解成与可见线程数量相等的分块,并每个线程赋予一个分块,就像【OpenMP】 2.1 简单示例-CSDN博客中multi_block函数的实现方式一样。

        如果手动指定了分块大小,那么OMP会将循环分成连续的指定大小的分块,并通过轮询调度的方式分配给每个线程。通过静态调度对复杂度相近的代码段可以实现较高的并行性能;同时需要确保每个线程与各自缓存层次之间的相互配合,增加数据从缓存中重用的机会,减少因为数据移动与内存带宽等导致的计算等待。

        示例如下,代码中每个线程独立处理一部分连续的数据块:

#include <stdio.h>
#include <math.h>
#include <omp.h>
#include <iostream>#define ITER 20int main() {int i;double A[ITER];for (i = 0; i < ITER; ++i)A[i] = 2.0 * i;#pragma omp parallel default(none) shared(A, std::cout){int i;int id = omp_get_thread_num();double tdata = omp_get_wtime();/*使用静态调度,每次每个线程独立处理4个连续的数据*/
#pragma omp for schedule(static, 5)for (i = 1; i < ITER; i++) {//避免输出错乱
# pragma omp criticalstd::cout << "当前线程id: " << omp_get_thread_num() << "正在处理的数据的索引: " << i << std::endl;A[i] = A[i] * sqrt(i) / pow(sin(i), tan(i));}tdata = omp_get_wtime() - tdata;if (id == 0) printf("Time spent is %f sec \n", tdata);}
}

        结果:

当前线程id: 1正在处理的数据的索引: 6
当前线程id: 3正在处理的数据的索引: 16
当前线程id: 0正在处理的数据的索引: 1
当前线程id: 0正在处理的数据的索引: 2
当前线程id: 1正在处理的数据的索引: 7
当前线程id: 0正在处理的数据的索引: 3
当前线程id: 3正在处理的数据的索引: 17
当前线程id: 0正在处理的数据的索引: 4
当前线程id: 3正在处理的数据的索引: 18
当前线程id: 0正在处理的数据的索引: 5
当前线程id: 3正在处理的数据的索引: 19
当前线程id: 1正在处理的数据的索引: 8
当前线程id: 1正在处理的数据的索引: 9
当前线程id: 1正在处理的数据的索引: 10
当前线程id: 2正在处理的数据的索引: 11
当前线程id: 2正在处理的数据的索引: 12
当前线程id: 2正在处理的数据的索引: 13
当前线程id: 2正在处理的数据的索引: 14
当前线程id: 2正在处理的数据的索引: 15
Time spent is 0.000420 sec Process finished with exit code 0

3.2 动态调度(dynamic)

        当每次迭代中代码的运行时间大致一样时,静态调度可以发挥最大的性能;但是当每次迭代中运行的时间不能确定时,比如粒子模拟的代码,有的粒子需要大量的计算,有的粒子则不需要;这样的迭代在静态调度的轮询算法中,可能把所有都需要大量计算的部分块都分给了一个线程执行,那么其余线程早早执行完自己的部分就会在原地等待直到最后一个线程处理完成。也有可能在当前的多核心异构系统中,比如Intel12代及以上的CPU中包含大小核架构的处理器中,核心以不同的频率运行,使得有些线程的运行速度会更快的完成工作;如果使用静态调度器则无法考虑到这种差异,都会使得部分线程等待,降低并行性能。

        上述两种情况都需要在运行时才可以知道每个线程的工作量,因此omp中提供了动态调度dynamic。

        下述代码中,通过do_sometng函数来模型不同任务的处理时长变化的情况,并通过动态调度,每个线程每次分配一个独立的任务让其处理。

#include <omp.h>
#include <iostream>
#include <chrono>   // std::chrono::seconds
#include <thread>   // std::this_thread::sleep_for#define ITER 20void do_someting(int seconds) {std::this_thread::sleep_for(std::chrono::seconds(seconds));
}int main() {srand((unsigned) time(0));#pragma omp parallel default(none) shared( std::cout){int i;int id = omp_get_thread_num();double tdata = omp_get_wtime();/*使用动态调度,每次每个线程独立处理1个的数据*/
#pragma omp for schedule(dynamic, 1)for (i = 1; i < ITER; i++) {int sleep_seconds = int(rand()) % 3;//避免输出错乱
# pragma omp criticalstd::cout << "当前线程id: " << omp_get_thread_num()<< "当前数据将要处理: " << sleep_seconds << "秒。" << std::endl;do_someting(sleep_seconds);}tdata = omp_get_wtime() - tdata;if (id == 0) printf("Time spent is %f sec \n", tdata);}
}

        结果可以看到,线程3因为每次都要处理较长的时间,所以只处理了20个任务中的3个任务;而线程0因为任务较轻,所以处理了6个任务;可以看出动态调度根据每个线程的耗时进行任务的动态分配;避免了某些线程执行完后等待其他线程的情况。

当前线程id: 2当前数据将要处理: 1秒。
当前线程id: 3当前数据将要处理: 1秒。
当前线程id: 1当前数据将要处理: 0秒。
当前线程id: 1当前数据将要处理: 2秒。
当前线程id: 0当前数据将要处理: 1秒。
当前线程id: 2当前数据将要处理: 0秒。
当前线程id: 2当前数据将要处理: 1秒。
当前线程id: 3当前数据将要处理: 1秒。
当前线程id: 0当前数据将要处理: 0秒。
当前线程id: 0当前数据将要处理: 0秒。
当前线程id: 0当前数据将要处理: 0秒。
当前线程id: 0当前数据将要处理: 2秒。
当前线程id: 2当前数据将要处理: 2秒。
当前线程id: 1当前数据将要处理: 1秒。
当前线程id: 3当前数据将要处理: 2秒。
当前线程id: 1当前数据将要处理: 0秒。
当前线程id: 1当前数据将要处理: 2秒。
当前线程id: 0当前数据将要处理: 1秒。
当前线程id: 2当前数据将要处理: 2秒。
Time spent is 6.000432 sec

如果是静态调度,则每个线程都要独立处理5任务(总共20个任务,4个可用线程);修改动态调度代码为:

#pragma omp for schedule(static, 5)

那么结果如下,可以看到部分线程完成任务后需要等待其他线程执行完后才能一起退出。

当前线程id: 3当前数据将要处理: 0秒。
当前线程id: 2当前数据将要处理: 2秒。
当前线程id: 3当前数据将要处理: 1秒。
当前线程id: 1当前数据将要处理: 2秒。
当前线程id: 0当前数据将要处理: 2秒。
当前线程id: 3当前数据将要处理: 1秒。
当前线程id: 2当前数据将要处理: 0秒。
当前线程id: 1当前数据将要处理: 1秒。
当前线程id: 2当前数据将要处理: 0秒。
当前线程id: 2当前数据将要处理: 0秒。
当前线程id: 2当前数据将要处理: 2秒。
当前线程id: 0当前数据将要处理: 2秒。
当前线程id: 3当前数据将要处理: 1秒。
当前线程id: 1当前数据将要处理: 1秒。
当前线程id: 0当前数据将要处理: 2秒。
当前线程id: 1当前数据将要处理: 0秒。
当前线程id: 1当前数据将要处理: 1秒。
当前线程id: 0当前数据将要处理: 1秒。
当前线程id: 0当前数据将要处理: 1秒。
Time spent is 8.000411 sec Process finished with exit code 0

调度的选择

调度方式对比
schedule子句静态调度(static)动态调度(dynamic)
默认块大小11
何时使用每次迭代运行时间相近每次迭代运行时间方差大
调度器时间开销

        虽然动态调度看上去很不错,但是需要注意的是动态调度提供了线程间的自动负载均衡,其调度器的开销是巨大的;如果可以将不同复杂度的任务均匀的分配到各个线程中,则最好使用多个chunk为一组的静态调度器。

        如下是对均匀复杂度的数据使用静态调度与动态调度的时间对比:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/621601.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【GitHub项目推荐--6 个吊炸天的后台模板】【转载】

很多程序员都有过接私活的经历&#xff0c;帮别人开发一个网站&#xff1f;写个软件&#xff1f;不少网站都要有一个后台管理系统&#xff0c;而后台管理系统大多数情况下仅仅是管理员在使用&#xff0c;所以不像前台那样需要去定制设计优美的 UI。 一套既美观又方便的后台框架…

国家注册信息安全专业人员十五类CISP证书

国家注册信息安全专业人员&#xff08;Certified Information Security Professiona&#xff0c;简称CISP&#xff09;&#xff0c;是面向党政机关、关键信息基础设施运营单位、各类企事业单位和社会组织以及网络与信息安全企业、测评和咨询服务机构等工作的信息安全人员颁发的…

第 4 课 创建工作空间与功能包

文章目录 第 4 课 创建工作空间与功能包1.工作环境的创建2.ROS功能包的创建 第 4 课 创建工作空间与功能包 消息和服务的创建、发布器和订阅器的编写、服务端和客户端的编写都是基于Ros功能包进行操作的&#xff0c;因此在进行上述操作前&#xff0c;需要先创建工作空间及功能包…

注释的魔力:HTML、JS/jQuery和CSS中的单行与多行注释

HTML注释&#xff1a; 在HTML中&#xff0c;我们使用<!--和-->来创建单行注释。例如&#xff1a; <!-- 这是单行注释 -->而多行注释也类似例如&#xff1a; <!DOCTYPE html> <html><!--这是多行注释这是多行注释这是多行注释--> </html>…

【信息论安全】:信源编码定理

一. 介绍 在点对点的通信中&#xff0c;信源编码定理&#xff08;source coding theorem&#xff09;满足可达性和可逆性。当信道是无噪声时&#xff0c;那么YX&#xff0c;这时就不需要信道编码。但是&#xff0c;信源编码依旧是有效的&#xff0c;可以提高数据传输效率&…

iOS swift UISlider改变进度条的高度和圆形滑块的大小

文章目录 1.改变进度条的高度&#xff08;亲测有效&#xff09;2.改变圆形滑块的大小&#xff08;亲测有效&#xff09; 1.改变进度条的高度&#xff08;亲测有效&#xff09; import UIKitclass CustomSlider: UISlider {// 设置轨道高度var trackHeight: CGFloat 10// 重写…

Navicat 16 for MySQL:打造高效数据库开发管理工具

随着数据的快速增长和复杂性的提升&#xff0c;数据库成为了现代应用开发中不可或缺的一部分。而在MySQL数据库领域&#xff0c;Navicat 16 for MySQL作为一款强大的数据库开发管理工具&#xff0c;正受到越来越多开发者的青睐。 Navicat 16 for MySQL拥有丰富的功能和直观的界…

Jenkins-Maven Git

整合Maven 安装GIT #更新yum sudo yum update #安装git yum install git 安装Maven插件,在插件管理中心&#xff1a; 配置仓库 配置密码认证 我们可以在这个目录下看到Jenkins 帮我们拉取了代码 /env/liyong/data/docker/jenkins_mount/workspace/maven-job 配置maven打包…

[数据结构与算法]数据结构基础、排序算法详解、算法思想详解、领域算法详解------

# 数据结构基础 学习思路 避免孤立的学习知识点&#xff0c;要关联学习。比如实际应用当中&#xff0c;我们经常使用的是查找和排序操作&#xff0c;这在我们的各种管理系统、数据库系统、操作系统等当中&#xff0c;十分常用&#xff0c;我们通过这个线索将知识点串联起来&am…

go-zero

官网地址 go-zero初探 常见问题及常用命令 1、配置go的镜像&#xff0c;存在的可以不用进行配置&#xff0c;用官方的镜像下载太慢或下载不下来 go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct 2、自动生成api后需要运行一下go mod tidy。用来加载…

启英泰伦推出「离线自然说」,离线语音交互随意说,不需记忆词条

离线语音识别是指不需要依赖网络&#xff0c;在本地设备实现语音识别的过程&#xff0c;通常以端侧AI语音芯片作为载体来进行数据的采集、计算和决策。但是语音芯片的存储空间有限&#xff0c;通过传统的语音算法技术&#xff0c;最多也只能存储数百条词条&#xff0c;导致用户…

python 入门基础 Introduction to Python Fundamentals

文章目录 注释单行注释多行注释 pass字符串格式化format%f-string(py3.6之后可用) 数据结构intstrboolfloat列表(list)字典(dict)集合枚举迭代器其他元组(tuple) 数值运算流程控制文件操作函数面向对象类方法继承与多态 对象复制上下文管理 异常模块包属性异步网络 注释 单行注…

Elasticsearch 索引文档时create、index、update的区别【学习记录】

本文基于elasticsearch7.3.0版本。 一、思维导图 elasticsearch中create、index、update都可以实现插入功能&#xff0c;但是实现原理并不相同。 二、验证index和create 由上面思维导图可以清晰的看出create、index的大致区别&#xff0c;下面我们来验证下思维导图中的场景&…

照片删除了怎么恢复回来

照片&#xff0c;对我们来说&#xff0c;这两个字眼再熟悉不过了&#xff0c;每一张照片都包含无比重要的意义&#xff0c;相信在大家的心目中&#xff0c;这些包含意义的照片都是无价的。怎样找回删除的照片&#xff1f; 既然这些照片对我们来说意义非凡&#xff0c;那如果不小…

通过 C++/WinRT 实现高级并发和异步

将工作卸载到 Windows 线程池 协同例程与任何其他函数的类似之处在于&#xff0c;调用方将会阻塞到某个函数向其返回了执行为止。 另外&#xff0c;协同例程返回的第一个机会是第一个 co_await、co_return 或 co_yield。 因此&#xff0c;在协同例程中执行受计算限制的工作之…

使用Guava Retrying优雅的实现业务异常重试

上次写过一篇如何使用spring retry来实现业务重试的文章&#xff1a;https://blog.csdn.net/Kingsea442/article/details/135341747 尽管 Spring Retry 工具能够优雅地实现重试&#xff0c;但它仍然存在两个不太友好的设计&#xff1a; 重试实体被限定为 Throwable 子类&#…

c++多久会被Python或者新语言取代?

c多久会被Python或者新语言取代&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&am…

方法重写。

altinsert 静态方法和非静态方法不一样 重写只与非静态方法有关 重载是指在一个类中定义多个同名的方法或者函数&#xff0c;但是这些方法或者函数的参数列表不同&#xff0c;即参数的类型、个数或者顺序不同。当调用这个同名方法或者函数时&#xff0c;编译器会根据实际传入的…

Elasticsearch的基本功能和使用

Elasticsearch &#xff0c;简称为 ES&#xff0c;是一款非常强大的开源的高扩展的分布式全文 检索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容,它可以近乎实时的 存储、检索数据.还可以可以实现日志统计、分析、系统监控等功能. 官网:https://www.elastic.c…

QT+jenkins window环境实现一键自动化构建打包签名发布

jenkins + QT 自动化构建打包 1.官网下载地址: Jenkins download and deployment,下载最新版本的安装包并安装。安装过程中,会要求你输入端口号并记住。 2.java下载地址:Java Downloads | Oracle,下载最新版本的安装包并安装。 3.浏览器输入网址:127.0.0.1: port, port为…