OpenMP使用详解

一、OpenMP基本概念

OpenMP是一种用于共享内存并行系统的多线程程序设计方案,支持的编程语言包括C、C++和Fortran。OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。当编译器不支持OpenMP时,程序会退化成普通(串行)程序。程序中已有的OpenMP指令不会影响程序的正常编译运行。在VS中启用OpenMP很简单,很多主流的编译环境都内置了OpenMP。(具体介绍可以参考OpenMP总结

二、OpenMP执行模式

OpenMP采用fork-join的执行模式。开始的时候只存在一个主线程,当需要进行并行计算的时候,派生出若干个分支线程来执行并行任务。当并行代码执行完成之后,分支线程会合,并把控制流程交给单独的主线程。
一个典型的fork-join执行模型的示意图如下:


在这里插入图片描述

OpenMP编程模型以线程为基础,通过编译制导指令制导并行化,有三种编程要素可以实现并行化控制,他们分别是编译制导、API函数集和环境变量。

三、编译制导

编译制导指令以#pragma omp 开始,后边跟具体的功能指令,格式如:#pragma omp 指令[子句[,子句] …]。

3.1 常用的功能指令

功能指令 解析
parallel 用在一个结构块之前,表示这段代码将被多个线程并行执行
for 用于for循环语句之前,表示将循环计算任务分配到多个线程中并行执行,以实现任务分担,必须由编程人员自己保证每次循环之间无数据相关性
sections 用在可被并行执行的代码段之前,用于实现多个结构块语句的任务分担,可并行执行的代码段各自用section指令标出(注意区分sections和section)
parallel sections parallel和sections两个语句的结合,类似于parallel for
single 用在并行域内,表示一段只被单个线程执行的代码
critical 用在一段代码临界区之前,保证每次只有一个OpenMP线程进入
flush 保证各个OpenMP线程的数据影像的一致性
barrier 用于并行域内代码的线程同步,线程执行到barrier时要停下等待,直到所有线程都执行到barrier时才继续往下执行
atomic 用于指定一个数据操作需要原子性地完成
master 用于指定一段代码由主线程执行
threadprivate 用于指定一个或多个变量是线程专用,后面会解释线程专有和私有的区别

3.2 相应的OpenMP子句

OpenMP子句 解析
private 指定一个或多个变量在每个线程中都有它自己的私有副本
firstprivate 指定一个或多个变量在每个线程都有它自己的私有副本,并且私有变量要在进入并行域或任务分担域时,继承主线程中的同名变量的值作为初值
lastprivate 是用来指定将线程中的一个或多个私有变量的值在并行处理结束后复制到主线程中的同名变量中,负责拷贝的线程是for或sections任务分担中的最后一个线程
reduction 用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的归约运算,并将结果返回给主线程同名变量
nowait 指出并发线程可以忽略其他制导指令暗含的路障同步
num_threads 指定并行域内的线程的数目
schedule 指定for任务分担中的任务分配调度类型
shared 指定一个或多个变量为多个线程间的共享变量
ordered 用来指定for任务分担域内指定代码段需要按照串行循环次序执行
copyprivate 配合single指令,将指定线程的专有变量广播到并行域内其他线程的同名变量中
copyinn 用来指定一个threadprivate类型的变量需要用主线程同名变量进行初始化
default 用来指定并行域内的变量的使用方式,缺省是shared

四、API函数

除上述编译制导指令之外,OpenMP还提供了一组API函数用于控制并发线程的某些行为,下面是一些常用的OpenMP API函数以及说明

函数名 作用
omp_in_parallel 判断当前是否在并行域中
omp_get_thread_num 返回线程号
omp_set_num_thread 设置后续并行域中的线程格式
omp_get_num_threads 返回当前并行域中的线程数
omp_get_max_threads 返回并行域可用的最大线程数目
omp_get_num_prpces 返回系统中处理器的数目
omp_get_dynamic 判断是否支持动态改变线程数目
omp_set_dynamic 启用或关闭线程数目的动态改变
omp_get_nested 判断系统是否支持并行嵌套
omp_set_nested 启用或关闭并行嵌套

五、环境变量

OpenMP中定义一些环境变量,可以通过这些环境变量控制OpenMP程序的行为,常用的环境变量

环境变量 解析
OMP_SCHEDULE 用于for循环并行化后的调度,它的值就是循环调度的类型
OMP_NUM_THREADS 用于设置并行域中的线程数
OMP_DYNAMIC 通过设定变量值,来确定是否允许动态设定并行域内的线程数
OMP_NESTED 指出是否可以并行嵌套

六、简单示例

6.1 parallel使用

parallel制导指令用来创建并行域,后边要跟一个大括号将要并行执行的代码放在一起
test_para.cpp

#include<iostream>
#include"omp.h"
using namespace std;
int main()
{
#pragma omp parallel
  {
    cout << "Test" << endl;
  }
 return 0;
}

编译:g++ -fopenmp test_para.cpp -o test_para
运行./op_test
结果:打印了16个Test(笔者电脑是16核,所以打印16个)

6.2 paraller for使用

使用parallel制导指令只是产生了并行域,让多个线程分别执行相同的任务,并没有实际的使用价值。parallel for用于生成一个并行域,并将计算任务在多个线程之间分配,从而加快计算运行的速度。可以让系统默认分配线程个数,也可以使用num_threads子句指定线程个数。
test_parafor.c

#include<stdio.h>
#include <stdlib.h>
#include<omp.h>
int main(int argc,char** argv)
{
    #pragma omp parallel for num_threads(6)
    for (int i = 0; i < 12; i++)
    {
    printf("OpenMP Test, 线程编号为: %d\n", omp_get_thread_num());
    }
    return 0;
 }

编译:gcc -fopenmp test_parafor.c -o test_parafor
运行./test_parafor
结果:如下图


上边程序指定了6个线程,迭代量为12,从输出可以看到每个线程都分到了12/6=2次的迭代量。

6.3 OpenMP效率提升以及不同线程数效率对比

diff_threads.c

#include <stdlib.h>
#include <stdio.h>
#include "omp.h"

void test()
{
    for (int i = 0; i < 80000; i++)
    {
        //do something
    }
}

int main(int argc, char **argv)

{

    float startTime = omp_get_wtime();
    //指定2个线程
#pragma omp parallel for num_threads(2)
    for (int i = 0; i < 80000; i++)
    {
        test();
    }
    float endTime = omp_get_wtime();
    printf("指定 2 个线程,执行时间: %f\n", endTime - startTime);
    startTime = endTime;
    //指定4个线程
#pragma omp parallel for num_threads(4)
    for (int i = 0; i < 80000; i++)
    {
        test();
    }
    endTime = omp_get_wtime();
    printf("指定 4 个线程,执行时间: %f\n", endTime - startTime);
    startTime = endTime;
    //指定8个线程
#pragma omp parallel for num_threads(8)
    for (int i = 0; i < 80000; i++)
    {
        test();
    }
    endTime = omp_get_wtime();
    printf("指定 8 个线程,执行时间: %f\n", endTime - startTime);
    startTime = endTime;
    //指定12个线程

#pragma omp parallel for num_threads(12)
    for (int i = 0; i < 80000; i++)
    {
        test();
    }
    endTime = omp_get_wtime();
    printf("指定 12 个线程,执行时间: %f\n", endTime - startTime);
    startTime = endTime;
    //不使用OpenMP
    for (int i = 0; i < 80000; i++)
    {
        test();
    }
    endTime = omp_get_wtime();
    printf("不使用OpenMP多线程,执行时间: %f\n", endTime - startTime);
    startTime = endTime;
    return 0;
}

编译:gcc -fopenmp diff_threads.c -o diff_threads
运行./diff_threads
结果:如下图

可见,使用OpenMP优化后的程序执行时间是原来的1/4左右,并且并不是线程数使用越多效率越高,一般线程数达到4~8个的时候,不能简单通过提高线程数来进一步提高效率。

6.4 API使用

API.c

#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    printf("ID: %d, Max threads: %d, Num threads: %d \n", omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());
    omp_set_num_threads(5);
    printf("ID: %d, Max threads: %d, Num threads: %d \n", omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

#pragma omp parallel num_threads(5)
    {
        // omp_set_num_threads(6);  // Do not call it in parallel region
        printf("ID: %d, Max threads: %d, Num threads: %d \n", omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());
    }

    printf("ID: %d, Max threads: %d, Num threads: %d \n", omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

    omp_set_num_threads(6);
    printf("ID: %d, Max threads: %d, Num threads: %d \n", omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

    return 0;
}

编译:gcc -fopenmp API.c -o api
运行./api
结果:如下图

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 在上一篇中我们介绍了在 cython 中使用 mpi4py 的方法,下面我们将介绍 mpi4py 与 OpenMP...
    自可乐阅读 2,459评论 0 3
  • OpenMP2.5 有底层API后,就已经可以实现并行编程;然而,很多时候串行算法已经成型,如果继续使用原有的底层...
    一梦换须臾_阅读 1,378评论 0 0
  • 本篇文章只是记录api的用法和回顾,方便记忆 openMP openMP提供“基于指令”的共享内存API。这就意味...
    Gavinjou大笨象阅读 13,257评论 0 7
  • 目前很多移动端的深度学习前向运算框架都用到openmp,如ncnn、paddlelite、mace等。所以这篇来介...
    半笔闪阅读 2,403评论 0 2
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 3,525评论 0 5