当前位置:网站首页>OpenMP入门
OpenMP入门
2022-06-25 06:39:00 【AI小白龙】
OpenMP 是 Open MultiProcessing 的缩写。可以在 Visual Studio 或者 gcc 中使用。
Hello World
把下面的代码保存为 omp.cc
| #include <iostream> #include <omp.h> int main() { #pragma omp parallel for for (char i = 'a'; i <= 'z'; i++) std::cout << i << std::endl; return 0; } |
然后 g++ omp.cc -fopenmp就可以了
循环的并行化
OpenMP的设计们希望提供一种简单的方式让程序员不需要懂得创建和销毁线程就能写出多线程化程序。为此他们设计了一些pragma,指令和函数来让编译器能够在合适的地方插入线程大多数的循环只需要在for之前插入一个pragma就可以实现并行化。而且,通过把这些恼人的细节都丢给编译器,你可以花费更多的时间来决定哪里需要多线程和优化数据结构
下面个这个例子把32位的RGB颜色转换成8位的灰度数据,你只需要在for之前加上一句pragma就可以实现并行化了
| #pragma omp parallel for for (int i = 0; i < pixelCount; i++) { grayBitmap[i] = (uint8_t)(rgbBitmap[i].r * 0.229 + rgbBitmap[i].g * 0.587 + rgbBitmap[i].b * 0.114); } |
神奇吧,首先,这个例子使用了“work sharing”,当“work sharing”被用在for循环的时候,每个循环都被分配到了不同的线程,并且保证只执行一次。OpenMP决定了多少线程需要被打开,销毁和创建,你需要做的就是告诉OpenMP哪里需要被线程化。
OpenMP 对可以多线程化的循环有如下五个要求:
循环的变量变量(就是i)必须是有符号整形,其他的都不行。
循环的比较条件必须是< <= > >=中的一种
循环的增量部分必须是增减一个不变的值(即每次循环是不变的)。
如果比较符号是< <=,那每次循环i应该增加,反之应该减小
循环必须是没有奇奇怪怪的东西,不能从内部循环跳到外部循环,goto和break只能在循环内部跳转,异常必须在循环内部被捕获。
如果你的循环不符合这些条件,那就只好改写了
检测是否支持 OpenMP
| #ifndef _OPENMP fprintf(stderr, "OpenMP not supported"); #endif |
避免数据依赖和竞争
当一个循环满足以上五个条件时,依然可能因为数据依赖而不能够合理的并行化。当两个不同的迭代之间的数据存在依赖关系时,就会发生这种情况。
| // 假设数组已经初始化为1 #pragma omp parallel for for (int i = 2; i < 10; i++) { factorial[i] = i * factorial[i-1]; } |
编译器会把这个循环多线程化,但是并不能实现我们想要的加速效果,得出的数组含有错误的结构。因为每次迭代都依赖于另一个不同的迭代,这被称之为竞态条件。要解决这个问题只能够重写循环或者选择不同的算法。
竞态条件很难被检测到,因为也有可能恰好程序是按你想要的顺序执行的。
管理公有和私有数据
基本上每个循环都会读写数据,确定那个数据时线程之间共有的,那些数据时线程私有的就是程序员的责任了。当数据被设置为公有的时候,所有的线程访问的都是相同的内存地址,当数据被设为私有的时候,每个线程都有自己的一份拷贝。默认情况下,除了循环变量以外,所有数据都被设定为公有的。可以通过以下两种方法把变量设置为私有的:
在循环内部声明变量,注意不要是static的
通过OpenMP指令声明私有变量
| // 下面这个例子是错误的 int temp; // 在循环之外声明 #pragma omp parallel for for (int i = 0; i < 100; i++) { temp = array[i]; array[i] = doSomething(temp); } |
可以通过以下两种方法改正
| // 1. 在循环内部声明变量 #pragma omp parallel for for (int i = 0; i < 100; i++) { int temp = array[i]; array[i] = doSomething(temp); } |
| // 2. 通过OpenMP指令说明私有变量 int temp; #pragma omp parallel for private(temp) for (int i = 0; i < 100; i++) { temp = array[i]; array[i] = doSomething(temp); } |
Reductions
一种常见的循环就是累加变量,对此,OpenMP 有专门的语句
例如下面的程序:
| int sum = 0; for (int i = 0; i < 100; i++) { sum += array[i]; // sum需要私有才能实现并行化,但是又必须是公有的才能产生正确结果 } |
上面的这个程序里,sum公有或者私有都不对,为了解决这个问题,OpenMP 提供了reduction语句;
| int sum = 0; #pragma omp parallel for reduction(+:sum) for (int i = 0; i < 100; i++) { sum += array[i]; } |
内部实现中,OpenMP 为每个线程提供了私有的sum变量,当线程退出时,OpenMP 再把每个线程的部分和加在一起得到最终结果。
当然,OpenMP 不止能做累加,凡是累计运算都是可以的,如下表:
循环调度
负载均衡是多线程程序中对性能影响最大的因素了,只有实现了负载均衡才能保证所有的核心都是忙的,而不会出现空闲时间。如果没有负载均衡, 有一些线程会远远早于其他线程结束, 导致处理器空闲浪费优化的可能.
在循环中,经常会由于每次迭代的相差时间较大和破坏负载平衡。通常可以通过检查源码来发现循环的变动可能. 大多数情况下每次迭代可能会发现大概一致的时间,当这个条件不能满足的时候,你可能能找到一个花费了大概一致时间的子集。例如, 有时候所有偶数循环花费了和所有奇数循环一样的时间, 有时候可能前一半循环和后一半循环花费了相似的时间. 另一方面, 有时候你可能找不到花费相同时间的一组循环. 不论如何, 你应该把这些信息提供给 OpenMP, 这样才能让 OpenMP 有更好的机会去优化循环.
默认情况下,OpenMP认为所有的循环迭代运行的时间都是一样的,这就导致了OpenMP会把不同的迭代等分到不同的核心上,并且让他们分布的尽可能减小内存访问冲突,这样做是因为循环一般会线性地访问内存, 所以把循环按照前一半后一半的方法分配可以最大程度的减少冲突. 然而对内存访问来说这可能是最好的方法, 但是对于负载均衡可能并不是最好的方法, 而且反过来最好的负载均衡可能也会破坏内存访问. 因此必须折衷考虑.
OpenMP 负载均衡使用下面的语法
| #pragma omp parallel for schedule(kind [, chunk size]) |
其中kind可以是下面的这些类型, 而 chunk size 则必须是循环不变的正整数
例子
| #pragma omp parallel for for (int i = 0; i < numElements; i++) { array[i] = initValue; initValue++; } |
显然这个循环里就有了竞态条件, 每个循环都依赖于 initValue 这个变量, 我们需要去掉它.
| #pragma omp parallel for for (int i = 0; i < numElements; i++) { array[i] = initValue + i; } |
这样就可以了, 因为现在我们没有让 initValue 去被依赖
所以, 对于一个循环来说, 应该尽可能地把 loop-variant 变量建立在 i 上.
边栏推荐
- [C language] one dimensional array
- STL教程4-输入输出流和对象序列化
- 从感知机到Transformer,一文概述深度学习简史
- Ns32f103c8t6 can perfectly replace stm32f103c8t6
- Construction of occupancy grid map
- Chuantuwei ca-is3720lw alternative material No. iso7820fdw
- 鸿蒙页面菜单的选择
- 不同路径II[针对DFS的动态规划改进]
- 为什么要“除夕”,原来是内存爆了!
- Enter an integer with any number of bits, and output the sum of each bit of the number. For example: 1234 – > 10
猜你喜欢

This year, I graduated

Sichuan earth microelectronics high performance, high integration and low cost isolated 485 transceiver

Chuantu microelectronics high speed and high performance rs-485/422 transceiver series

Classic paper in the field of character recognition: aster

Tupu software digital twin 3D wind farm, offshore wind power of smart wind power

VectorDraw Web Library 10.10

Why "New Year's Eve", the original memory burst!

MySQL facet 01
![对链表进行插入排序[dummy统一操作+断链核心--被动节点]](/img/2a/ccb1145d2b4f9fbd8d0812deace93b.png)
对链表进行插入排序[dummy统一操作+断链核心--被动节点]

用太极拳讲分布式理论,真舒服!
随机推荐
【LeetCode】two num·两数之和
The e-book "action guide for large organizations to further promote zero code application platform" was officially released!
shell小技巧(一百三十四)简单的键盘输入记录器
C#入门教程
Kube scheduler source code analysis (1) - initialization and startup analysis
LabVIEW jump to web page
MySQL facet 01
Cglib dynamic proxy
SQL solve select basic statement
【批处理DOS-CMD命令-汇总和小结】-文件与目录操作命令(md、rd、xcopy、dir、cd、set、move、copy、del、type、sort)
对链表进行插入排序[dummy统一操作+断链核心--被动节点]
The principle of Zener diode, what is its function?
【批處理DOS-CMD命令-匯總和小結】-外部命令-cmd下載命令、抓包命令(wget)
Mysql database import SQL file display garbled code
Introduction to Sichuan Tuwei ca-is3082wx isolated rs-485/rs-422 transceiver
[pytest] modify the logo and parameterization in the allure Report
【Qt】快捷键
My debut is finished!
GUI pull-down menu of unity3d evil door implementation dropdown design has no duplicate items
Debug through yalc before releasing NPM package