OpenMP 入门教程
前两天(其实是几个月以前了)看到了代码中有 #pragma omp parallel for 一段,感觉好像是 OpenMP,以前看到并行化的东西都是直接躲开,既然躲不开了,不妨研究一下:
OpenMP 是 Open MultiProcessing 的缩写。OpenMP 并不是一个简单的函数库,而是一个诸多编译器支持的框架,或者说是协议吧,总之,不需要任何配置,你就可以在 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 不止能做累加,凡是累计运算都是可以的,如下表:
| 操作 | 私有临时变量初值 |
|---|---|
| +、- | 0 |
| * | 1 |
| & | ~0 |
| |
0 |
| ^ | 0 |
| && | 1(true) |
|| |
0(false |
循环调度
负载均衡是多线程程序中对性能影响最大的因素了,只有实现了负载均衡才能保证所有的核心都是忙的,而不会出现空闲时间。如果没有负载均衡, 有一些线程会远远早于其他线程结束, 导致处理器空闲浪费优化的可能.
在循环中,经常会由于每次迭代的相差时间较大和破坏负载平衡。通常可以通过检查源码来发现循环的变动可能. 大多数情况下每次迭代可能会发现大概一致的时间,当这个条件不能满足的时候,你可能能找到一个花费了大概一致时间的子集。例如, 有时候所有偶数循环花费了和所有奇数循环一样的时间, 有时候可能前一半循环和后一半循环花费了相似的时间. 另一方面, 有时候你可能找不到花费相同时间的一组循环. 不论如何, 你应该把这些信息提供给 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 上.
待续...
OpenMP 入门教程的更多相关文章
- OpenMP入门教程(三)
承接前面两篇,这里直接逐一介绍和使用有关OpenMP的指令和函数 Directives 1.for 作用:for指令指定紧随其后的程序的循环的迭代必须由团队并行执行,只是假设已经建立了并行区域,否则它 ...
- OpenMP入门教程(二)
OpenMP API概述 OpenMP由三部分组成: 编译指令(19) 运行时库程序(32) 环境变量(9) 后来的API包含同样的三个组件,只是三者的数量都有所增加. 编译器指令 OpenMP编译器 ...
- OpenMP入门教程(一)
什么是OpenMP Open Multi-Processing的缩写,是一个应用程序接口(API),可用于显式指导多线程.共享内存的并行性. 在项目程序已经完成好的情况下不需要大幅度的修改源代码,只需 ...
- wepack+sass+vue 入门教程(三)
十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...
- wepack+sass+vue 入门教程(二)
六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...
- wepack+sass+vue 入门教程(一)
一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...
- Content Security Policy 入门教程
阮一峰文章:Content Security Policy 入门教程
- gulp详细入门教程
本文链接:http://www.ydcss.com/archives/18 gulp详细入门教程 简介: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器:她不仅能对网站资源进行优 ...
- UE4新手引导入门教程
请大家去这个地址下载:file:///D:/UE4%20Doc/虚幻4新手引导入门教程.pdf
随机推荐
- Mysql5.7服务下载安装
身处MySQL这个圈子,能够切身地感受到大家对MySQL 5.7的期待和热情,似乎每个人都迫不及待的想要了解.学习和使用MySQL 5.7.那么,我们不禁要问,MySQL 5.7到底做了哪些改进,引入 ...
- MonkeyRunner之小白如何使用MonkeyRecorder录制回放脚本
之前摸索了好久.学习Python语言.安装工具.拉拉溜溜也慢慢地一点点进步.每天就疯狂的上网找资料.虽然大牛们写的很详细.但是自己就是笨的不知怎么做.最后找了一篇文章,真的就是万事俱备只欠东风的感觉, ...
- 基于EM的多直线拟合
作者:桂. 时间:2017-03-22 06:13:50 链接:http://www.cnblogs.com/xingshansi/p/6597796.html 声明:欢迎被转载,不过记得注明出处哦 ...
- 使用Hibernate中出现了Caused by: java.sql.SQLException: Field 'gid' doesn't have a default value
那是因为表中没有设置主键自动增长,只需要改变表中的主键设置为自动增长即可
- 2017-3-22 HTML 表单 、框架
表单:<form action="" method="get/post" ></form> 表单元素:12个 1.文本类 文本框:< ...
- wcf、web api、webservicer 之间的区别
名次注解 SOAP 简单对象访问协议(SOAP)是一种轻量的.简单的.基于 XML 的协议,是交换数据的一种协议规范,是一种轻量的.简单的.基于XML(标准通用标记语言下的一个子集)的协议,它被设计成 ...
- x战警 天启高清完整版下载
天启出生于埃及第一王朝,是地球上最古老.最强大的变种人沙巴泊,曾拥有无数信众,但后来遭人背叛,被人活埋.几千年后,强大无匹而且永生不朽的天启从数千年的深埋中获释,愤怒的他发现同类不再被视为神祇,他对人 ...
- 环境搭建-VMware安装系统
常用的虚拟化软件: 1.vmware:收费 commercial(商业版的) 2.vitualbox:free 虚拟化软件:vmware workstation(虚拟系统工作站) 安装系统任务 ...
- Swing入门
厌倦了在控制台使用键盘输入并显示结果的过程?是的,在你现在这台电脑上,已经很少有程序使用这种交互方式.本实验将带你初步进入图形用户界面(GUI)的世界,让你学会如何编写屏幕上那些具有特定大小和位置的窗 ...
- iphone与安卓的兼容性问题汇总
1.日期问题 当使用yyyy-mm-dd格式时,iphone不认,安卓没问题 解决办法:new Date(res.data[i].inventoryDate.replace(/-/g, "/ ...