前两天(其实是几个月以前了)看到了代码中有 #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 对可以多线程化的循环有如下五个要求:

  1. 循环的变量变量(就是i)必须是有符号整形,其他的都不行。
  2. 循环的比较条件必须是< <= > >=中的一种
  3. 循环的增量部分必须是增减一个不变的值(即每次循环是不变的)。
  4. 如果比较符号是< <=,那每次循环i应该增加,反之应该减小
  5. 循环必须是没有奇奇怪怪的东西,不能从内部循环跳到外部循环,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];
}

编译器会把这个循环多线程化,但是并不能实现我们想要的加速效果,得出的数组含有错误的结构。因为每次迭代都依赖于另一个不同的迭代,这被称之为竞态条件。要解决这个问题只能够重写循环或者选择不同的算法。

竞态条件很难被检测到,因为也有可能恰好程序是按你想要的顺序执行的。

管理公有和私有数据

基本上每个循环都会读写数据,确定那个数据时线程之间共有的,那些数据时线程私有的就是程序员的责任了。当数据被设置为公有的时候,所有的线程访问的都是相同的内存地址,当数据被设为私有的时候,每个线程都有自己的一份拷贝。默认情况下,除了循环变量以外,所有数据都被设定为公有的。可以通过以下两种方法把变量设置为私有的:

  1. 在循环内部声明变量,注意不要是static的
  2. 通过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 入门教程的更多相关文章

  1. OpenMP入门教程(三)

    承接前面两篇,这里直接逐一介绍和使用有关OpenMP的指令和函数 Directives 1.for 作用:for指令指定紧随其后的程序的循环的迭代必须由团队并行执行,只是假设已经建立了并行区域,否则它 ...

  2. OpenMP入门教程(二)

    OpenMP API概述 OpenMP由三部分组成: 编译指令(19) 运行时库程序(32) 环境变量(9) 后来的API包含同样的三个组件,只是三者的数量都有所增加. 编译器指令 OpenMP编译器 ...

  3. OpenMP入门教程(一)

    什么是OpenMP Open Multi-Processing的缩写,是一个应用程序接口(API),可用于显式指导多线程.共享内存的并行性. 在项目程序已经完成好的情况下不需要大幅度的修改源代码,只需 ...

  4. wepack+sass+vue 入门教程(三)

    十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...

  5. wepack+sass+vue 入门教程(二)

    六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...

  6. wepack+sass+vue 入门教程(一)

    一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...

  7. Content Security Policy 入门教程

    阮一峰文章:Content Security Policy 入门教程

  8. gulp详细入门教程

    本文链接:http://www.ydcss.com/archives/18 gulp详细入门教程 简介: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器:她不仅能对网站资源进行优 ...

  9. UE4新手引导入门教程

    请大家去这个地址下载:file:///D:/UE4%20Doc/虚幻4新手引导入门教程.pdf

随机推荐

  1. mybatis源码跟踪

    第一步:从web.xml进入dispatcherServlet进入前端控制器 第二步:使用handlerMapping 获得handlerChain 第三步:使用适配器执行handler获取model ...

  2. CSS3 transition 浏览器兼容性

    1.兼容性 根据canius(http://caniuse.com/#search=transition),transition 兼容性如下图所示: <!DOCTYPE html> < ...

  3. Android开发之音乐播放器

    做了一天的音乐播放器小项目,已经上传到github,将链接发到这里供大家参阅提议 https://github.com/wangpeng0531/MusicPlayer.git

  4. Spring MVC 处理异常的3种方式

    使用Spring MVC开发的博客网站时,遇到了如何处理业务层抛出的异常的问题,查阅到了spring官方博客-spring MVC中异常的处理,以下将会以登录模块为示例. 愚蠢的处理方式 处理异常遵循 ...

  5. React文档翻译系列(一)安装

    原文地址:原文 本系列是针对React文档进行的翻译,因为自己在学习react的时候,最开始通过看博客或者论坛等中文资料,有些内容是零零散散的接收,并没有给自己带来很好的效果,所以后来决定把文档的原文 ...

  6. C语言 动态数组实现

    一.概述 C语言是不能直接定义动态数组的,数组必须在初始化时确定长度. 如果要在程序运行时才确定数组的长度,就需要在运行的时候,自己去向系统申请一块内存用动态内存分配实现动态数组. 二.动态内存分配函 ...

  7. API测试自动化——基于CDIF的SOA基本功能(实例篇)

    今天我们通过一些实例来体验一下API的自动化测试,感受一下基于CDIF的SOA的一些基本功能. 传统的测试工具在测试一个API的时候,必须手动填写这个API所需要接收的所有信息,比如一个查询航班动态的 ...

  8. Linux之shell编程函数使用

    linux shell 可以用户定义函数,然后在shell脚本中可以随便调用.下面说说它的定义方法,以及调用需要注意那些事项. 原文和作者一起讨论:http://www.cnblogs.com/int ...

  9. (原)centos7安装和使用greenplum4.3.12(详细版)

     对于很多IT人来说GREENPLUM是个陌生的名字.简单的说它就是一个与ORACLE, DB2一样面向对象的关系型数据库.我们通过标准的SQL可以对GP中的数据进行访问存取. 本质上讲GREENPL ...

  10. cooking构建工具报错MSBUILD :error MSB4132解决办法

    最近学习cooking构建工具的时候,在自己的笔记本上运行的好好的,项目在公司电脑上clone下来的时候,发现构建报错,逐条查错,试了好多方法也不行 最后在github上找到了答案,只是之前一直没找到 ...