当前位置:网站首页>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 上.
边栏推荐
- LabVIEW generate application (exe) and installer
- 栅格地图(occupancy grid map)构建
- Common functions of OrCAD schematic
- 我与CSDN的一年时光及大学经验分享
- 线程状态变化涉及哪些常用 API
- IAR compiler flashback
- Unity3D邪门实现之GUI下拉菜单Dropdown设计无重复项
- Large funds support ecological construction, and Plato farm builds a real meta universe with Dao as its governance
- Ns32f103c8t6 can perfectly replace stm32f103c8t6
- [batch dos-cmd command - summary and summary] - external command -cmd download command and packet capture command (WGet)
猜你喜欢
三年营收连续下滑,天地壹号困在醋饮料里
Domestic MCU perfectly replaces STM chip model of Italy France
LabVIEW jump to web page
不同路径II[针对DFS的动态规划改进]
realsense d455 semantic_slam实现语义八叉树建图
用太极拳讲分布式理论,真舒服!
Application scheme | application of Sichuan earth microelectronics ca-is398x in PLC field
Omni toolbox direct download
Path planner based on time potential function in dynamic environment
Construction of occupancy grid map
随机推荐
诸葛亮 VS 庞统,拿下分布式 Paxos
稳压二极管的原理,它有什么作用?
Advanced mathematics foundation_ Parity of functions
Classic paper in the field of character recognition: aster
【蒸馏】PointDistiller: Structured Knowledge DistillationTowards Efficient and Compact 3D Detection
Sqlmap advanced use – cookies
[batch dos-cmd command - summary and summary] - CMD window setting and operation commands (CD, title, mode, color, pause, CHCP, exit)
Hanxin's trick: consistent hashing
What common APIs are involved in thread state changes
为什么要“除夕”,原来是内存爆了!
Ca-is1200u current detection isolation amplifier has been delivered in batch
Weimeisi new energy rushes to the scientific innovation board: the annual revenue is 1.7 billion, and the book value of accounts receivable is nearly 400million
Selection of Hongmeng page menu
Authentique Photoshop 2022 expérience d'achat partage
MySQL(十二)——更改表的备注
[batch dos-cmd command - summary and summary] - add comment command (REM or::)
LabVIEW jump to web page
ELK + filebeat日志解析、日志入库优化 、logstash过滤器配置属性
Can I open a stock account with a compass? Is it safe?
How comfortable it is to use Taijiquan to talk about distributed theory!