OpenMP

OpenMP

OpenMP是一種支持多平臺的共享內(nèi)存是越、多處理器(多線程)的規(guī)范API曹鸠。OpenMP的API包括編譯器偽指令(pragma指令)造成、運行時函數(shù)搞旭、環(huán)境變量幾個部分趁餐。OpenMP簡化了并行代碼的編寫喷兼,許多細(xì)節(jié)都由庫本身去管理(但依賴關(guān)系、讀寫沖突后雷、死鎖等要靠開發(fā)者)季惯,同時如果系統(tǒng)不支持OpenMP,代碼也會直接采用串行執(zhí)行方式臀突。 OpenMP適用的情況是基于共享內(nèi)存模型勉抓,如果是非共享內(nèi)存模型(例如分布式),則更適合適用OpenMPI候学。

1. 編譯

使用了OpenMP的程序要在編譯時加入-fopenmp參數(shù)

g++ -fopenmp filename.cpp

2. 執(zhí)行模型

OpenMP線程采用的是fork-join方式藕筋,在需要產(chǎn)生線程的地方通過fork的方式產(chǎn)生線程并使用共享內(nèi)存的方式來并行,最后自動使用join回收線程梳码。

比較新的OpenMP支持嵌套線程(nest)隐圾,在外層循環(huán)中執(zhí)行到parallel指定的位置時伍掀,OpenMP會產(chǎn)生多個線程,接著如果在內(nèi)層循環(huán)中再遇到了parallel指定的并行暇藏,則會再產(chǎn)生多個線程蜜笤。但這個功能需要系統(tǒng)支持,不支持的系統(tǒng)會忽略內(nèi)層的并行盐碱。

  1. task:相當(dāng)于一個函數(shù)部分(就像是ray中的task一樣把兔,是任務(wù)的一部分),可以使用task構(gòu)造塊或者section指令構(gòu)造塊瓮顽,其必須在聲明了parallel指令的循環(huán)內(nèi)县好。
  1. target: 用于異構(gòu)并行計算內(nèi)容,執(zhí)行到target構(gòu)造塊的代碼時暖混,系統(tǒng)會在加速器上映射相關(guān)的數(shù)據(jù)結(jié)構(gòu)聘惦,用加速器來運行代碼。

3. 內(nèi)存模型

OpenMP中儒恋,所有線程都可以訪問同一個內(nèi)存空間善绎,但是由于弱內(nèi)存一致性,每個線程在同一時刻看到在內(nèi)存空間中的同一變量可能值是不一致的诫尽。

  1. 塊內(nèi)變量訪問權(quán)限:
    • shared(default):并行區(qū)域訪問到的是真正的變量禀酱。
    • private:訪問到的僅僅是副本。
  1. flush操作:會刷新內(nèi)存空間內(nèi)變量的值牧嫉,但是并不保證順序剂跟,所以還是可能會出現(xiàn)不一致。

4. 環(huán)境變量

通過修改環(huán)境變量的值酣藻,可以對OpenMP進行定制曹洽。

環(huán)境變量名 描述
OMP_SCHEDULE 運行時的負(fù)載均衡類型與循環(huán)次數(shù)
OMP_NUM_THREADS 并行區(qū)域的默認(rèn)線程數(shù)
OMP_DYNAMIC 是否可以動態(tài)調(diào)整并行區(qū)域的線程數(shù)(ture or false)
OMP_PROC_BIND 是否允許遷移線程(切換處理器),如果為true辽剧,則不會遷移
OMP_NESTED 是否支持嵌套并行區(qū)域
OMP_STACKSIZE 指定每個線程的棧的大小
OMP_THREAD_LIMIT 指定了線程最多被創(chuàng)建的數(shù)量

5. 鎖函數(shù)

OpenMP中送淆,鎖的數(shù)據(jù)類型為omp_lock_t,其基本功能就是保證線程同步怕轿。以下為常見的幾個函數(shù):

函數(shù)名 描述
void omp_init_lock(omp_lock_t *); 初始化線程鎖
void omp_set_lock(omp_lock_t *); 獲得線程鎖
void omp_nuset_lock(omp_lock_t *); 釋放線程鎖
void omp_destroy_lock(omp_lock_t *); 銷毀線程鎖

注:初始化和銷毀鎖的操作必須由主線程在并行區(qū)域外執(zhí)行偷崩。

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

int main(int argc, char *argv[])
{
    int x = 3, y = 4;
    
    // 初始化鎖
    omp_lock_t lock;
    omp_init_lock(&lock);
    
    // 偽指令設(shè)置并行,使用num_threads()設(shè)置線程數(shù)撞羽,
    // 并且將x阐斜、y設(shè)置成shared狀態(tài)
    // 花括號必須在下一行,而不能在#pragma后面
    #pragma omp parallel num_threads(3) shared(x, y)
    {
        omp_set_lock(&lock);  // 線程獲得鎖诀紊,保證同步
        
        x += omp_get_thread_num();  // 當(dāng)前的線程id
        y += omp_get_thread_num();
        
        omp_unset_lock(&lock);  // 釋放鎖
    }
    
    printf("x = %d, y = %d\n", x, y);
    
    omp_destroy_lock(&lock);
    
    return 0;
}

6. 構(gòu)造

  1. parallel

    #pragma omp parallel num_threads(3) shared(x, y)
    

    parallel構(gòu)造塊里的代碼是將同一份代碼復(fù)制到多個線程共同運行谒出,也就是說同一個任務(wù)被多個線程執(zhí)行了。

    parallel構(gòu)造塊有如下限制

    • 花括號必須在下一行,而不能在#pragma后面

    • 不能使用nowait()

```c++
#include<iostream>
#include<omp.h>
using namespace std;

void test_omp_parallel() {
    #pragma omp parallel num_threads(3)
    for (int i = 0; i < 3; i++)
    {
        cout << "Hello, I am " << omp_get_thread_num() << ", iter " << i << endl;  // 當(dāng)前的線程id
    }
}

int main(int argc, char* argv[])
{
    test_omp_parallel();
    return 0;
}
```
  1. for

    #pragma omp parallel for num_threads(3) shared(x, y)
    

    parallel for構(gòu)造塊里的代碼是將任務(wù)分割分配到多個進程中笤喳,實現(xiàn)并行完成一個任務(wù)考赛。

    需要注意的是,OpenMP支持的for構(gòu)造形式有以下限制

    • 可推測循環(huán)次數(shù)莉测、索引是整型、自增步長不變

    • 循環(huán)是單出口單入口唧喉,不允許使用break捣卤、gotoreturn等語句跳出循環(huán)八孝。

    • 不能在循環(huán)內(nèi)部拋出異常董朝,但可以使用exit()退出整個程序,其余的線程也會同步退出干跛。

```c++
#include<iostream>
#include<omp.h>
using namespace std;

void test_omp_for() {
    #pragma omp parallel for num_threads(3)
    for (int i = 0; i < 3; i++)
    {
        cout << "Hello, I am " << omp_get_thread_num() << ", iter " << i << endl;  // 當(dāng)前的線程id
    }
}

int main(int argc, char* argv[])
{
    test_omp_for();
    return 0;
}
```
  1. simd

    #pragma omp simd 子句
    

    SIMD可以使得代碼向量化子姜,也有點像SSE、AVX等向量化指令集楼入,需要OpenMP 4.0以上的版本才支持該偽指令哥捕。可以單獨使用嘉熊,也可以和parallel遥赚、parallel for聯(lián)合使用。

simd有以下子句:

*   **safelen(x)**:其后緊隨的循環(huán)中阐肤,每x次循環(huán)都是互不相關(guān)的(也就是每個線程內(nèi)的數(shù)據(jù)是不相關(guān)的)凫佛。
*   **aligned(list:n)**:對數(shù)組list對齊尺寸為n個字節(jié)。
*   **reduction(+:ret)**:每個循環(huán)的數(shù)組都是部分?jǐn)?shù)據(jù)孕惜,最后將其整合成完整的數(shù)據(jù)愧薛。

```c++
#include<iostream>
#include<omp.h>
using namespace std;

void test_omp_simd() {
    int a[4] = { 1, 2, 3, 4 };
    int ret=0;
    #pragma omp parallel for simd reduction(+:ret)
    {
        for (int i = 0; i < 4; i++) {
            ret += a[i] * a[i];
        }
    }

    cout << "ret: " << ret << endl;
}

int main(int argc, char* argv[])
{
    test_omp_simd();
    return 0;
}
```
  1. task

    #pragma omp task 子句
    

    task主要就是為了方便分解任務(wù),但需要注意的是衫画,task構(gòu)造必須在parallel構(gòu)造塊之中運行毫炉,且必須在parallel構(gòu)造塊中使用single子句。如果需要父線程等待所有子線程完成才繼續(xù)執(zhí)行削罩,則需要使用taskwait子句碘箍。

遞歸計算Fibonacci數(shù)列例子

```c++
#include<iostream>
#include<omp.h>
using namespace std;

int facobi(int num) {
    // 遞歸退出條件
    if ((0 == num) || (1 == num)) {
        return 1;
    }

    int f1, f2;

    #pragma omp task shared(f1)
    f1 = facobi(num - 1);

    #pragma omp task shared(f2)
    f2 = facobi(num - 2);

    #pragma omp taskwait
    return f1 + f2;  // 使用taskwait語句,等待f1和f2計算結(jié)束再返回
}


void test_omp_task() {
    int r;

    #pragma omp parallel shared(r)
    {
        #pragma omp single
        r = facobi(5);       // 必須使用single子句鲸郊,指定該任務(wù)只有1個線程運行
    }
    
    cout << "r: " << r << endl;
}


int main(int argc, char* argv[])
{
    test_omp_task();

    return 0;
}
```
  1. sections/section

    將代碼分成幾個段落丰榴,每個段落使用一個線程執(zhí)行,互不相干秆撮。

    #pragma omp parallel section
    
  1. single

    指定某些代碼只使用一個線程運行四濒,是僅對某些代碼進行特殊處理時才使用的。

    #pragma omp single
    
  1. barrier

    柵欄同步是指要執(zhí)行當(dāng)前線程,必須保證這一部分之前的所有線程都執(zhí)行完畢(比如每個并行任務(wù)中有幾塊分開的并行區(qū)域盗蟆,每一個區(qū)域執(zhí)行前戈二,必須保證上一個區(qū)域的所有線程都已執(zhí)行完)。barrier則是顯式調(diào)用柵欄同步喳资。

    #include<iostream>
    #include<omp.h>
    using namespace std;
    
    void test_omp_barrier() {
        int a[6];
    
        #pragma omp parallel num_threads(3) shared(a)
        {
            int id = omp_get_thread_num();  // 當(dāng)前的線程id
            a[id] = id;
            a[3 + id] = 3 + id;
    
            #pragma omp barrier
            int swap = a[id];
            a[id] = a[5 - id];
            a[5 - id] = swap;
        }
    
        for (int i = 0; i < 6; i++) cout << "a[" << i << "]: " << a[i] << endl;
    }
    
    
    int main(int argc, char* argv[])
    {
        test_omp_barrier();
    
        return 0;
    }
    
  1. proc_bind

    處理器映射策略觉吭,有以下幾種策略

    • spread:均勻分布在各個核心

    • close:盡量分布在相鄰的處理器

    • master:主線程和子線程都在同一處理器

  1. nowait

    顯示地取消了柵欄同步。

7. 子句

  1. collapse

    為了負(fù)載均衡(例如緊隨其后的兩個循環(huán)仆邓,每個循環(huán)都較邢侍病),合并之后任務(wù)較小的循環(huán)节值。

    // 意思是指定接下來的兩層循環(huán)直接線程化
    #pragma omp parallel for collapse(2)
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < num; j++){
            // .....
        }
    }
    
  1. private

    將某個線程私有化徙硅,每個線程擁有變量的副本,且不允許其他線程染指這些變量搞疗。

  1. shared

    聲明某些變量是共享的嗓蘑,但必須保證讀寫的正確性。

  1. reduction

    在并行區(qū)域結(jié)束時匿乃,對指定的某個變量進行某個計算操作(例如+桩皿,支持的運算符只有少數(shù))。

  1. schedule

    schedule(type[, size])
    

    其中幢炸,size是可選參數(shù)业簿,表示每次分發(fā)任務(wù)的循環(huán)迭代次數(shù)。而type為策略阳懂,主要有以下幾種

    • static:靜態(tài)負(fù)載均衡策略梅尤,設(shè)任務(wù)長度為K, 而線程數(shù)量為N岩调,則每個線程分得的任務(wù)為 K/N巷燥。因為 K/N不一定是整數(shù),所以存在一定的負(fù)載均衡問題号枕。
    • dynamic:動態(tài)負(fù)載均衡策略缰揪,根據(jù)設(shè)定的size(或默認(rèn))將任務(wù)分割,加入工作隊列葱淳,哪個線程空閑就去領(lǐng)取新的任務(wù)钝腺,直到所有任務(wù)被執(zhí)行完畢。
    • runtime:由環(huán)境變量 OMP_SCHEDULE的值來確定負(fù)載均衡策略赞厕。
  1. if

    根據(jù)所給條件艳狐,來決定該區(qū)域什么時候并行,什么時候不采取并行皿桑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毫目,一起剝皮案震驚了整個濱河市蔬啡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镀虐,老刑警劉巖箱蟆,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刮便,居然都是意外死亡空猜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門恨旱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辈毯,“玉大人,你說我怎么就攤上這事窖杀。” “怎么了裙士?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵入客,是天一觀的道長。 經(jīng)常有香客問我腿椎,道長桌硫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任啃炸,我火速辦了婚禮铆隘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘南用。我一直安慰自己膀钠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布裹虫。 她就那樣靜靜地躺著肿嘲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筑公。 梳的紋絲不亂的頭發(fā)上雳窟,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音匣屡,去河邊找鬼封救。 笑死,一個胖子當(dāng)著我的面吹牛捣作,可吹牛的內(nèi)容都是我干的誉结。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼券躁,長吁一口氣:“原來是場噩夢啊……” “哼搓彻!你這毒婦竟也來了如绸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤旭贬,失蹤者是張志新(化名)和其女友劉穎怔接,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稀轨,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡扼脐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奋刽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓦侮。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖佣谐,靈堂內(nèi)的尸體忽然破棺而出肚吏,到底是詐尸還是另有隱情,我是刑警寧澤狭魂,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布罚攀,位于F島的核電站,受9級特大地震影響雌澄,放射性物質(zhì)發(fā)生泄漏斋泄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一镐牺、第九天 我趴在偏房一處隱蔽的房頂上張望炫掐。 院中可真熱鬧,春花似錦睬涧、人聲如沸募胃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摔认。三九已至,卻和暖如春宅粥,著一層夾襖步出監(jiān)牢的瞬間参袱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工秽梅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抹蚀,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓企垦,卻偏偏與公主長得像环壤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钞诡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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