OpenMP使用詳解

一、OpenMP基本概念

OpenMP是一種用于共享內(nèi)存并行系統(tǒng)的多線程程序設(shè)計方案,支持的編程語言包括C、C++和Fortran。OpenMP提供了對并行算法的高層抽象描述,特別適合在多核CPU機器上的并行程序設(shè)計。編譯器根據(jù)程序中添加的pragma指令,自動將程序并行處理,使用OpenMP降低了并行編程的難度和復(fù)雜度。當編譯器不支持OpenMP時,程序會退化成普通(串行)程序。程序中已有的OpenMP指令不會影響程序的正常編譯運行。在VS中啟用OpenMP很簡單,很多主流的編譯環(huán)境都內(nèi)置了OpenMP。(具體介紹可以參考OpenMP總結(jié)

二、OpenMP執(zhí)行模式

OpenMP采用fork-join的執(zhí)行模式。開始的時候只存在一個主線程,當需要進行并行計算的時候,派生出若干個分支線程來執(zhí)行并行任務(wù)。當并行代碼執(zhí)行完成之后,分支線程會合,并把控制流程交給單獨的主線程。
一個典型的fork-join執(zhí)行模型的示意圖如下:


在這里插入圖片描述

OpenMP編程模型以線程為基礎(chǔ),通過編譯制導(dǎo)指令制導(dǎo)并行化,有三種編程要素可以實現(xiàn)并行化控制,他們分別是編譯制導(dǎo)、API函數(shù)集和環(huán)境變量。

三、編譯制導(dǎo)

編譯制導(dǎo)指令以#pragma omp 開始,后邊跟具體的功能指令,格式如:#pragma omp 指令[子句[,子句] …]。

3.1 常用的功能指令

功能指令 解析
parallel 用在一個結(jié)構(gòu)塊之前,表示這段代碼將被多個線程并行執(zhí)行
for 用于for循環(huán)語句之前,表示將循環(huán)計算任務(wù)分配到多個線程中并行執(zhí)行,以實現(xiàn)任務(wù)分擔(dān),必須由編程人員自己保證每次循環(huán)之間無數(shù)據(jù)相關(guān)性
sections 用在可被并行執(zhí)行的代碼段之前,用于實現(xiàn)多個結(jié)構(gòu)塊語句的任務(wù)分擔(dān),可并行執(zhí)行的代碼段各自用section指令標出(注意區(qū)分sections和section)
parallel sections parallel和sections兩個語句的結(jié)合,類似于parallel for
single 用在并行域內(nèi),表示一段只被單個線程執(zhí)行的代碼
critical 用在一段代碼臨界區(qū)之前,保證每次只有一個OpenMP線程進入
flush 保證各個OpenMP線程的數(shù)據(jù)影像的一致性
barrier 用于并行域內(nèi)代碼的線程同步,線程執(zhí)行到barrier時要停下等待,直到所有線程都執(zhí)行到barrier時才繼續(xù)往下執(zhí)行
atomic 用于指定一個數(shù)據(jù)操作需要原子性地完成
master 用于指定一段代碼由主線程執(zhí)行
threadprivate 用于指定一個或多個變量是線程專用,后面會解釋線程專有和私有的區(qū)別

3.2 相應(yīng)的OpenMP子句

OpenMP子句 解析
private 指定一個或多個變量在每個線程中都有它自己的私有副本
firstprivate 指定一個或多個變量在每個線程都有它自己的私有副本,并且私有變量要在進入并行域或任務(wù)分擔(dān)域時,繼承主線程中的同名變量的值作為初值
lastprivate 是用來指定將線程中的一個或多個私有變量的值在并行處理結(jié)束后復(fù)制到主線程中的同名變量中,負責(zé)拷貝的線程是for或sections任務(wù)分擔(dān)中的最后一個線程
reduction 用來指定一個或多個變量是私有的,并且在并行處理結(jié)束后這些變量要執(zhí)行指定的歸約運算,并將結(jié)果返回給主線程同名變量
nowait 指出并發(fā)線程可以忽略其他制導(dǎo)指令暗含的路障同步
num_threads 指定并行域內(nèi)的線程的數(shù)目
schedule 指定for任務(wù)分擔(dān)中的任務(wù)分配調(diào)度類型
shared 指定一個或多個變量為多個線程間的共享變量
ordered 用來指定for任務(wù)分擔(dān)域內(nèi)指定代碼段需要按照串行循環(huán)次序執(zhí)行
copyprivate 配合single指令,將指定線程的專有變量廣播到并行域內(nèi)其他線程的同名變量中
copyinn 用來指定一個threadprivate類型的變量需要用主線程同名變量進行初始化
default 用來指定并行域內(nèi)的變量的使用方式,缺省是shared

四、API函數(shù)

除上述編譯制導(dǎo)指令之外,OpenMP還提供了一組API函數(shù)用于控制并發(fā)線程的某些行為,下面是一些常用的OpenMP API函數(shù)以及說明

函數(shù)名 作用
omp_in_parallel 判斷當前是否在并行域中
omp_get_thread_num 返回線程號
omp_set_num_thread 設(shè)置后續(xù)并行域中的線程格式
omp_get_num_threads 返回當前并行域中的線程數(shù)
omp_get_max_threads 返回并行域可用的最大線程數(shù)目
omp_get_num_prpces 返回系統(tǒng)中處理器的數(shù)目
omp_get_dynamic 判斷是否支持動態(tài)改變線程數(shù)目
omp_set_dynamic 啟用或關(guān)閉線程數(shù)目的動態(tài)改變
omp_get_nested 判斷系統(tǒng)是否支持并行嵌套
omp_set_nested 啟用或關(guān)閉并行嵌套

五、環(huán)境變量

OpenMP中定義一些環(huán)境變量,可以通過這些環(huán)境變量控制OpenMP程序的行為,常用的環(huán)境變量

環(huán)境變量 解析
OMP_SCHEDULE 用于for循環(huán)并行化后的調(diào)度,它的值就是循環(huán)調(diào)度的類型
OMP_NUM_THREADS 用于設(shè)置并行域中的線程數(shù)
OMP_DYNAMIC 通過設(shè)定變量值,來確定是否允許動態(tài)設(shè)定并行域內(nèi)的線程數(shù)
OMP_NESTED 指出是否可以并行嵌套

六、簡單示例

6.1 parallel使用

parallel制導(dǎo)指令用來創(chuàng)建并行域,后邊要跟一個大括號將要并行執(zhí)行的代碼放在一起
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
結(jié)果:打印了16個Test(筆者電腦是16核,所以打印16個)

6.2 paraller for使用

使用parallel制導(dǎo)指令只是產(chǎn)生了并行域,讓多個線程分別執(zhí)行相同的任務(wù),并沒有實際的使用價值。parallel for用于生成一個并行域,并將計算任務(wù)在多個線程之間分配,從而加快計算運行的速度。可以讓系統(tǒng)默認分配線程個數(shù),也可以使用num_threads子句指定線程個數(shù)。
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
結(jié)果:如下圖


上邊程序指定了6個線程,迭代量為12,從輸出可以看到每個線程都分到了12/6=2次的迭代量。

6.3 OpenMP效率提升以及不同線程數(shù)效率對比

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 個線程,執(zhí)行時間: %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 個線程,執(zhí)行時間: %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 個線程,執(zhí)行時間: %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 個線程,執(zhí)行時間: %f\n", endTime - startTime);
    startTime = endTime;
    //不使用OpenMP
    for (int i = 0; i < 80000; i++)
    {
        test();
    }
    endTime = omp_get_wtime();
    printf("不使用OpenMP多線程,執(zhí)行時間: %f\n", endTime - startTime);
    startTime = endTime;
    return 0;
}

編譯:gcc -fopenmp diff_threads.c -o diff_threads
運行./diff_threads
結(jié)果:如下圖

可見,使用OpenMP優(yōu)化后的程序執(zhí)行時間是原來的1/4左右,并且并不是線程數(shù)使用越多效率越高,一般線程數(shù)達到4~8個的時候,不能簡單通過提高線程數(shù)來進一步提高效率。

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
結(jié)果:如下圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內(nèi)容