日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

OpenMP: OpenMP编程指南

發(fā)布時間:2025/7/25 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OpenMP: OpenMP编程指南 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
from: OpenMP: OpenMP編程指南

????????? 進(jìn)入多核時代后,必須使用多線程編寫程序才能讓各個CPU核得到利用。在單核時代,通常使用操作系統(tǒng)提供的API來創(chuàng)建線程,然而,在多核系統(tǒng)中,情況發(fā)生了很大的變化, 如果仍然使用操作系統(tǒng)API來創(chuàng)建線程會遇到一些問題。具體來說,有以下三個問題:

1)CPU核數(shù)擴(kuò)展性問題

????????? 多核編程需要考慮程序性能隨CPU核數(shù)的擴(kuò)展性,即硬件升級到更多核后,能夠不修改程序就讓程序性能增長,這要求程序中創(chuàng)建的線程數(shù)量需要隨CPU核數(shù)變化,不能創(chuàng)建固定數(shù)量的線程,否則在CPU核數(shù)超過線程數(shù)量上的機器上運行,將無法完全利用機器性能。雖然通過一定方法可以使用操作系統(tǒng)API創(chuàng)建可變化數(shù)量的線程,但是比較麻煩,不如OpenMP方便。

2)方便性問題

????????? 在多核編程時,要求計算均攤到各個CPU核上去,所有的程序都需要并行化執(zhí)行,對計算的負(fù)載均衡有很高要求。這就要求在同一個函數(shù)內(nèi)或同一個循環(huán)中,可能也需要將計算分?jǐn)偟礁鱾€CPU核上,需要創(chuàng)建多個線程。操作系統(tǒng)API創(chuàng)建線程時,需要線程入口函數(shù),很難滿足這個需求,除非將一個函數(shù)內(nèi)的代碼手工拆成多個線程入口函數(shù),這將大大增加程序員的工作量。使用OpenMP創(chuàng)建線程則不需要入口函數(shù),非常方便,可以將同一函數(shù)內(nèi)的代碼分解成多個線程執(zhí)行,也可以將一個for循環(huán)分解成多個線程執(zhí)行。

3)可移植性問題

???????? 目前各個主流操作系統(tǒng)的線程API互不兼容,缺乏事實上的統(tǒng)一規(guī)范,要滿足可移植性得自己寫一些代碼,將各種不同操作系統(tǒng)的api封裝成一套統(tǒng)一的接口。OpenMP是標(biāo)準(zhǔn)規(guī)范,所有支持它的編譯器都是執(zhí)行同一套標(biāo)準(zhǔn),不存在可移植性問題。

綜上所述,在多核編程中,使用OpenMP就很有必要,下面列出以前發(fā)表在我的CSDN博客中的OpenMP文章,供大家參考。

OpenMP并行程序設(shè)計(一) OpenMP是一個支持共享存儲并行設(shè)計的庫,特別適宜多核CPU上的并行程序設(shè)計。今天在雙核CPU機器上試了一下OpenMP并行程序設(shè)計,發(fā)現(xiàn)效率方面超出想象,因此寫出來分享給大家。 在VC8.0中項目的屬性對話框中,左邊框里的“配置屬性”下的“C/C++”下的“語言”頁里,將OpenMP支持改為“是/(OpenMP)”就可以支持OpenMP了。 先看一個簡單的使用了OpenMP程序 int main(int argc,char* argv[]) { #pragma omp parallelfor ???? for (int i = 0; i < 10; i++ ) ???? { ???????? printf("i = %d/n", i); ???? } ???? return 0; } 這個程序執(zhí)行后打印出以下結(jié)果: i = 0 i = 5 i = 1 i = 6 i = 2 i = 7 i = 3 i = 8 i = 4 i = 9 可見for 循環(huán)語句中的內(nèi)容被并行執(zhí)行了。(每次運行的打印結(jié)果可能會有區(qū)別) 這里要說明一下,#pragma omp parallelfor這條語句是用來指定后面的for循環(huán)語句變成并行執(zhí)行的,當(dāng)然for循環(huán)里的內(nèi)容必須滿足可以并行執(zhí)行,即每次循環(huán)互不相干,后一次循環(huán)不依賴于前面的循環(huán)。 有關(guān)#pragma omp parallelfor這條語句的具體含義及相關(guān)OpenMP指令和函數(shù)的介紹暫時先放一放,只要知道這條語句會將后面的for循環(huán)里的內(nèi)容變成并行執(zhí)行就行了。 ?將for循環(huán)里的語句變成并行執(zhí)行后效率會不會提高呢,我想這是我們最關(guān)心的內(nèi)容了。下面就寫一個簡單的測試程序來測試一下: void test() { ???? int a = 0; ???? clock_t t1 = clock(); ???? for (int i = 0; i < 100000000; i++) ???? { ???????? a = i+1; ???? } ???? clock_t t2 = clock(); ???? printf("Time = %d/n", t2-t1); } int main(int argc,char* argv[]) { ???? clock_t t1 = clock(); #pragma omp parallelfor ???? for (int j = 0; j < 2; j++ ){ ???????? test(); ???? } ???? clock_t t2 = clock(); ???? printf("Total time = %d/n", t2-t1); ???? test(); ???? return 0; } 在test()函數(shù)中,執(zhí)行了1億次循環(huán),主要是用來執(zhí)行一個長時間的操作。 在main()函數(shù)里,先在一個循環(huán)里調(diào)用test()函數(shù),只循環(huán)2次,我們還是看一下在雙核CPU上的運行結(jié)果吧: Time = 297 Time = 297 Total time = 297 Time = 297 可以看到在for循環(huán)里的兩次test()函數(shù)調(diào)用都花費了297ms, 但是打印出的總時間卻只花費了297ms,后面那個單獨執(zhí)行的test()函數(shù)花費的時間也是297ms,可見使用并行計算后效率提高了整整一倍。

OpenMP并行程序設(shè)計(二) 1、fork/join并行執(zhí)行模式的概念 OpenMP是一個編譯器指令和庫函數(shù)的集合,主要是為共享式存儲計算機上的并行程序設(shè)計使用的。 前面一篇文章中已經(jīng)試用了OpenMP的一個Parallel for指令。從上篇文章中我們也可以發(fā)現(xiàn)OpenMP并行執(zhí)行的程序要全部結(jié)束后才能執(zhí)行后面的非并行部分的代碼。這就是標(biāo)準(zhǔn)的并行模式fork/join式并行模式,共享存儲式并行程序就是使用fork/join式并行的。 標(biāo)準(zhǔn)并行模式執(zhí)行代碼的基本思想是,程序開始時只有一個主線程,程序中的串行部分都由主線程執(zhí)行,并行的部分是通過派生其他線程來執(zhí)行,但是如果并行部分沒有結(jié)束時是不會執(zhí)行串行部分的,如上一篇文章中的以下代碼: int main(int argc,char* argv[]) { ???? clock_t t1 = clock(); #pragma omp parallelfor ???? for ( int j = 0; j < 2; j++ ){ ???????? test(); ???? } ???? clock_t t2 = clock(); ???? printf("Total time = %d/n", t2-t1); ???? test(); ???? return 0; } 在沒有執(zhí)行完for循環(huán)中的代碼之前,后面的clock_t t2 = clock();這行代碼是不會執(zhí)行的,如果和調(diào)用線程創(chuàng)建函數(shù)相比,它相當(dāng)于先創(chuàng)建線程,并等待線程執(zhí)行完,所以這種并行模式中在主線程里創(chuàng)建的線程并沒有和主線程并行運行。 2、OpenMP指令和庫函數(shù)介紹 下面來介紹OpenMP的基本指令和常用指令的用法, 在C/C++中,OpenMP指令使用的格式為 ??????pragma omp指令 [子句[子句]…] 前面提到的parallel for就是一條指令,有些書中也將OpenMP的“指令”叫做“編譯指導(dǎo)語句”,后面的子句是可選的。例如: #pragma omp parallel private(i, j) parallel 就是指令, private是子句 為敘述方便把包含#pragma和OpenMP指令的一行叫做語句,如上面那行叫parallel語句。 OpenMP的指令有以下一些: parallel,用在一個代碼段之前,表示這段代碼將被多個線程并行執(zhí)行 for,用于for循環(huán)之前,將循環(huán)分配到多個線程中并行執(zhí)行,必須保證每次循環(huán)之間無相關(guān)性。 parallel for, parallel 和 for語句的結(jié)合,也是用在一個for循環(huán)之前,表示for循環(huán)的代碼將被多個線程并行執(zhí)行。 sections,用在可能會被并行執(zhí)行的代碼段之前 parallel sections,parallel和sections兩個語句的結(jié)合 critical,用在一段代碼臨界區(qū)之前 single,用在一段只被單個線程執(zhí)行的代碼段之前,表示后面的代碼段將被單線程執(zhí)行。 flush, barrier,用于并行區(qū)內(nèi)代碼的線程同步,所有線程執(zhí)行到barrier時要停止,直到所有線程都執(zhí)行到barrier時才繼續(xù)往下執(zhí)行。 atomic,用于指定一塊內(nèi)存區(qū)域被制動更新 master,用于指定一段代碼塊由主線程執(zhí)行 ordered, 用于指定并行區(qū)域的循環(huán)按順序執(zhí)行 threadprivate, 用于指定一個變量是線程私有的。 OpenMP除上述指令外,還有一些庫函數(shù),下面列出幾個常用的庫函數(shù): omp_get_num_procs, 返回運行本線程的多處理機的處理器個數(shù)。 omp_get_num_threads, 返回當(dāng)前并行區(qū)域中的活動線程個數(shù)。 omp_get_thread_num,?返回線程號 omp_set_num_threads,?設(shè)置并行執(zhí)行代碼時的線程個數(shù) omp_init_lock, 初始化一個簡單鎖 omp_set_lock, 上鎖操作 omp_unset_lock, 解鎖操作,要和omp_set_lock函數(shù)配對使用。 omp_destroy_lock, omp_init_lock函數(shù)的配對操作函數(shù),關(guān)閉一個鎖 OpenMP的子句有以下一些 private,指定每個線程都有它自己的變量私有副本。 firstprivate指定每個線程都有它自己的變量私有副本,并且變量要被繼承主線程中的初值。 lastprivate主要是用來指定將線程中的私有變量的值在并行處理結(jié)束后復(fù)制回主線程中的對應(yīng)變量。 reduce用來指定一個或多個變量是私有的,并且在并行處理結(jié)束后這些變量要執(zhí)行指定的運算。 nowait忽略指定中暗含的等待 num_threads指定線程的個數(shù) schedule指定如何調(diào)度for循環(huán)迭代 shared指定一個或多個變量為多個線程間的共享變量 ordered用來指定for循環(huán)的執(zhí)行要按順序執(zhí)行 copyprivate用于single指令中的指定變量為多個線程的共享變量 copyin用來指定一個threadprivate的變量的值要用主線程的值進(jìn)行初始化。 default用來指定并行處理區(qū)域內(nèi)的變量的使用方式,缺省是shared 3、parallel指令的用法 parallel 是用來構(gòu)造一個并行塊的,也可以使用其他指令如for、sections等和它配合使用。 在C/C++中,parallel的使用方法如下: #pragma omp parallel [for | sections] [子句[子句]…] { //代碼 } parallel語句后面要跟一個大括號對將要并行執(zhí)行的代碼括起來。 void main(int argc, char *argv[]) { #pragma omp parallel { printf(“Hello, World!/n”); } } 執(zhí)行以上代碼將會打印出以下結(jié)果 Hello, World! Hello, World! Hello, World! Hello, World! 可以看得出parallel語句中的代碼被執(zhí)行了四次,說明總共創(chuàng)建了4個線程去執(zhí)行parallel語句中的代碼。 也可以指定使用多少個線程來執(zhí)行,需要使用num_threads子句: void main(int argc, char *argv[]) { #pragma omp parallel num_threads(8) { printf(“Hello, World!, ThreadId=%d/n”, omp_get_thread_num() ); } } 執(zhí)行以上代碼,將會打印出以下結(jié)果: Hello, World!, ThreadId = 2 Hello, World!, ThreadId = 6 Hello, World!, ThreadId = 4 Hello, World!, ThreadId = 0 Hello, World!, ThreadId = 5 Hello, World!, ThreadId = 7 Hello, World!, ThreadId = 1 Hello, World!, ThreadId = 3 從ThreadId的不同可以看出創(chuàng)建了8個線程來執(zhí)行以上代碼。所以parallel指令是用來為一段代碼創(chuàng)建多個線程來執(zhí)行它的。parallel塊中的每行代碼都被多個線程重復(fù)執(zhí)行。 和傳統(tǒng)的創(chuàng)建線程函數(shù)比起來,相當(dāng)于為一個線程入口函數(shù)重復(fù)調(diào)用創(chuàng)建線程函數(shù)來創(chuàng)建線程并等待線程執(zhí)行完。 4、for指令的使用方法 for指令則是用來將一個for循環(huán)分配到多個線程中執(zhí)行。for指令一般可以和parallel指令合起來形成parallel for指令使用,也可以單獨用在parallel語句的并行塊中。 #pragma omp [parallel] for [子句] ????? for循環(huán)語句 先看看單獨使用for語句時是什么效果: int j = 0; #pragma ompfor ???? for ( j = 0; j < 4; j++ ){ ???????? printf(j = %d, ThreadId = %d/n, j, omp_get_thread_num()); ???? } 執(zhí)行以上代碼后打印出以下結(jié)果 j = 0, ThreadId = 0 j = 1, ThreadId = 0 j = 2, ThreadId = 0 j = 3, ThreadId = 0 從結(jié)果可以看出四次循環(huán)都在一個線程里執(zhí)行,可見for指令要和parallel指令結(jié)合起來使用才有效果: 如以下代碼就是parallel 和for一起結(jié)合成parallel for的形式使用的: int j = 0; #pragma omp parallelfor ???? for ( j = 0; j < 4; j++ ){ ???????? printf(j = %d, ThreadId = %d/n, j, omp_get_thread_num()); ???? } 執(zhí)行后會打印出以下結(jié)果: j = 0, ThreadId = 0 j = 2, ThreadId = 2 j = 1, ThreadId = 1 j = 3, ThreadId = 3 可見循環(huán)被分配到四個不同的線程中執(zhí)行。 上面這段代碼也可以改寫成以下形式: int j = 0; #pragma omp parallel { #pragma omp for ???? for ( j = 0; j < 4; j++ ){ ???????? printf(j = %d, ThreadId = %d/n, j, omp_get_thread_num()); ???? } } 執(zhí)行以上代碼會打印出以下結(jié)果: j = 1, ThreadId = 1 j = 3, ThreadId = 3 j = 2, ThreadId = 2 j = 0, ThreadId = 0 在一個parallel 塊中也可以有多個for語句,如: int j; #pragma omp parallel { #pragma omp for ???? for ( j = 0; j < 100; j++ ){ ???????? ???? } #pragma omp for ???? for ( ?j = 0; j < 100; j++ ){ ???????? ???? } } for 循環(huán)語句中,書寫是需要按照一定規(guī)范來寫才可以的,即for循環(huán)小括號內(nèi)的語句要按照一定的規(guī)范進(jìn)行書寫,for語句小括號里共有三條語句 for( i=start; i < end; i++) i=start; 是for循環(huán)里的第一條語句,必須寫成 “變量=初值” 的方式。如 i=0 i < end;是for循環(huán)里的第二條語句,這個語句里可以寫成以下4種形式之一: 變量 < 邊界值 變量 <= 邊界值 變量 > 邊界值 變量 >= 邊界值 如 i>10?i< 10i>=10?i>10 等等 最后一條語句i++可以有以下9種寫法之一
i++ ++i i-- --i i += inc i -= inc i = i + inc i = inc + i i = i –inc 例如i += 2;?i -= 2;i = i + 2;i = i - 2;都是符合規(guī)范的寫法。 5 sections和section指令的用法 section語句是用在sections語句里用來將sections語句里的代碼劃分成幾個不同的段,每段都并行執(zhí)行。用法如下: #pragma omp [parallel] sections [子句] { ?? #pragma omp section ?? { ??????????? 代碼塊 ?? }? } 先看一下以下的例子代碼: void main(int argc, char *argv) { #pragma omp parallel sections { #pragma omp section printf(“section 1 ThreadId = %d/n”, omp_get_thread_num()); #pragma omp section printf(“section 2 ThreadId = %d/n”, omp_get_thread_num()); #pragma omp section printf(“section 3 ThreadId = %d/n”, omp_get_thread_num()); #pragma omp section printf(“section 4 ThreadId = %d/n”, omp_get_thread_num()); } 執(zhí)行后將打印出以下結(jié)果: section 1 ThreadId = 0 section 2 ThreadId = 2 section 4 ThreadId = 3 section 3 ThreadId = 1 從結(jié)果中可以發(fā)現(xiàn)第4段代碼執(zhí)行比第3段代碼早,說明各個section里的代碼都是并行執(zhí)行的,并且各個section被分配到不同的線程執(zhí)行。 使用section語句時,需要注意的是這種方式需要保證各個section里的代碼執(zhí)行時間相差不大,否則某個section執(zhí)行時間比其他section過長就達(dá)不到并行執(zhí)行的效果了。 上面的代碼也可以改寫成以下形式: void main(int argc, char *argv) { #pragma omp parallel { #pragma omp sections { #pragma omp section printf(“section 1 ThreadId = %d/n”, omp_get_thread_num()); #pragma omp section printf(“section 2 ThreadId = %d/n”, omp_get_thread_num()); } #pragma omp sections { #pragma omp section printf(“section 3 ThreadId = %d/n”, omp_get_thread_num()); #pragma omp section printf(“section 4 ThreadId = %d/n”, omp_get_thread_num()); } } 執(zhí)行后將打印出以下結(jié)果: section 1 ThreadId = 0 section 2 ThreadId = 3 section 3 ThreadId = 3 section 4 ThreadId = 1 這種方式和前面那種方式的區(qū)別是,兩個sections語句是串行執(zhí)行的,即第二個sections語句里的代碼要等第一個sections語句里的代碼執(zhí)行完后才能執(zhí)行。 用for語句來分?jǐn)偸怯上到y(tǒng)自動進(jìn)行,只要每次循環(huán)間沒有時間上的差距,那么分?jǐn)偸呛芫鶆虻?#xff0c;使用section來劃分線程是一種手工劃分線程的方式,最終并行性的好壞得依賴于程序員。 本篇文章中講的幾個OpenMP指令parallel, for, sections, section實際上都是用來如何創(chuàng)建線程的,這種創(chuàng)建線程的方式比起傳統(tǒng)調(diào)用創(chuàng)建線程函數(shù)創(chuàng)建線程要更方便,并且更高效。 當(dāng)然,創(chuàng)建線程后,線程里的變量是共享的還是其他方式,主線程中定義的變量到了并行塊內(nèi)后還是和傳統(tǒng)創(chuàng)建線程那種方式一樣的嗎?創(chuàng)建的線程是如何調(diào)度的?等等諸如此類的問題到下一篇文章中進(jìn)行講解。

3、OpenMP中的數(shù)據(jù)處理子句????? (OpenMP程序設(shè)計的細(xì)節(jié))

OpenMP中的數(shù)據(jù)處理子句 相關(guān)文檔連接: 多核編程中的任務(wù)隨機競爭模式的概率分析 多核編程中的任務(wù)分組競爭模式? ?多核編程中的負(fù)載平衡難題 多核編程中的鎖競爭難題 多核編程的幾個難題及其應(yīng)對策略(難題一) OpenMP并行程序設(shè)計(二) ?OpenMP并行程序設(shè)計(一) ?雙核CPU上的快速排序效率? OpenMP創(chuàng)建線程中的鎖及原子操作性能比較 10.1.1?private子句 private子句用于將一個或多個變量聲明成線程私有的變量,變量聲明成私有變量后,指定每個線程都有它自己的變量私有副本,其他線程無法訪問私有副本。即使在并行區(qū)域外有同名的共享變量,共享變量在并行區(qū)域內(nèi)不起任何作用,并且并行區(qū)域內(nèi)不會操作到外面的共享變量。 private子句的用法格式如下: private(list) 下面便是一個使用private子句的代碼例子: int k = 100; #pragma omp parallel for private(k) for ( k=0; k < 10; k++) { printf("k=%d/n", k); } printf("last k=%d/n", k); 上面程序執(zhí)行后打印的結(jié)果如下: k=6 k=7 k=8 k=9 k=0 k=1 k=2 k=3 k=4 k=5 last k=100 從打印結(jié)果可以看出,for循環(huán)前的變量k和循環(huán)區(qū)域內(nèi)的變量k其實是兩個不同的變量。 用private子句聲明的私有變量的初始值在并行區(qū)域的入口處是未定義的,它并不會繼承同名共享變量的值。 出現(xiàn)在reduction子句中的參數(shù)不能出現(xiàn)在private子句中。 10.1.2?firstprivate子句 private聲明的私有變量不能繼承同名變量的值,但實際情況中有時需要繼承原有共享變量的值,OpenMP提供了firstprivate子句來實現(xiàn)這個功能。 先看一下以下的代碼例子 int k = 100; #pragma omp parallel for firstprivate(k) for ( i=0; i < 4; i++) { k+=i; printf("k=%d/n",k); } printf("last k=%d/n", k); 上面代碼執(zhí)行后打印結(jié)果如下: k=100 k=101 k=103 k=102 last k=100 從打印結(jié)果可以看出,并行區(qū)域內(nèi)的私有變量k繼承了外面共享變量k的值100作為初始值,并且在退出并行區(qū)域后,共享變量k的值保持為100未變。 10.1.3?lastprivate子句 有時在并行區(qū)域內(nèi)的私有變量的值經(jīng)過計算后,在退出并行區(qū)域時,需要將它的值賦給同名的共享變量,前面的private和firstprivate子句在退出并行區(qū)域時都沒有將私有變量的最后取值賦給對應(yīng)的共享變量,lastprivate子句就是用來實現(xiàn)在退出并行區(qū)域時將私有變量的值賦給共享變量。 舉個例子如下: int k = 100; #pragma omp parallel for firstprivate(k),lastprivate(k) for ( i=0; i < 4; i++) { k+=i; printf("k=%d/n",k); } printf("last k=%d/n", k); 上面代碼執(zhí)行后的打印結(jié)果如下: k=100 k=101 k=103 k=102 last k=103 從打印結(jié)果可以看出,退出for循環(huán)的并行區(qū)域后,共享變量k的值變成了103,而不是保持原來的100不變。 由于在并行區(qū)域內(nèi)是多個線程并行執(zhí)行的,最后到底是將那個線程的最終計算結(jié)果賦給了對應(yīng)的共享變量呢?OpenMP規(guī)范中指出,如果是循環(huán)迭代,那么是將最后一次循環(huán)迭代中的值賦給對應(yīng)的共享變量;如果是section構(gòu)造,那么是最后一個section語句中的值賦給對應(yīng)的共享變量。注意這里說的最后一個section是指程序語法上的最后一個,而不是實際運行時的最后一個運行完的。 如果是類(class)類型的變量使用在lastprivate參數(shù)中,那么使用時有些限制,需要一個可訪問的,明確的缺省構(gòu)造函數(shù),除非變量也被使用作為firstprivate子句的參數(shù);還需要一個拷貝賦值操作符,并且這個拷貝賦值操作符對于不同對象的操作順序是未指定的,依賴于編譯器的定義。 10.1.4?threadprivate子句 threadprivate子句用來指定全局的對象被各個線程各自復(fù)制了一個私有的拷貝,即各個線程具有各自私有的全局對象。 用法如下: #pragma omp threadprivate(list)new-line 下面用threadprivate命令來實現(xiàn)一個各個線程私有的計數(shù)器,各個線程使用同一個函數(shù)來實現(xiàn)自己的計數(shù)。計數(shù)器代碼如下: int counter = 0; #pragma omp threadprivate(counter) int increment_counter() { counter++; return(counter); } 如果對于靜態(tài)變量也同樣可以使用threadprivate聲明成線程私有的,上面的counter變量如改成用static類型來實現(xiàn)時,代碼如下: int increment_counter2() { static int counter = 0; #pragma omp threadprivate(counter) counter++; return(counter); } threadprivate和private的區(qū)別在于threadprivate聲明的變量通常是全局范圍內(nèi)有效的,而private聲明的變量只在它所屬的并行構(gòu)造中有效。 threadprivate的對應(yīng)只能用于copyin,copyprivate,schedule,num_threads和if子句中,不能用于任何其他子句中。 用作threadprivate的變量的地址不能是常數(shù)。 對于C++的類(class)類型變量,用作threadprivate的參數(shù)時有些限制,當(dāng)定義時帶有外部初始化時,必須具有明確的拷貝構(gòu)造函數(shù)。 對于windows系統(tǒng),threadprivate不能用于動態(tài)裝載(使用LoadLibrary裝載)的DLL中,可以用于靜態(tài)裝載的DLL中,關(guān)于windows系統(tǒng)中的更多限制,請參閱MSDN中有關(guān)threadprivate子句的幫助材料。 有關(guān)threadprivate命令的更多限制方面的信息,詳情請參閱OpenMP2.5規(guī)范。 10.1.5?shared子句 shared子句用來聲明一個或多個變量是共享變量。 用法如下: shared(list) 需要注意的是,在并行區(qū)域內(nèi)使用共享變量時,如果存在寫操作,必須對共享變量加以保護(hù),否則不要輕易使用共享變量,盡量將共享變量的訪問轉(zhuǎn)化為私有變量的訪問。 循環(huán)迭代變量在循環(huán)構(gòu)造區(qū)域里是私有的。聲明在循環(huán)構(gòu)造區(qū)域內(nèi)的自動變量都是私有的。 10.1.6?default子句 default子句用來允許用戶控制并行區(qū)域中變量的共享屬性。 用法如下: default(shared|none) 使用shared時,缺省情況下,傳入并行區(qū)域內(nèi)的同名變量被當(dāng)作共享變量來處理,不會產(chǎn)生線程私有副本,除非使用private等子句來指定某些變量為私有的才會產(chǎn)生副本。 如果使用none作為參數(shù),那么線程中用到的變量必須顯示指定是共享的還是私有的,除了那些由明確定義的除外。 10.1.7?reduction子句 reduction子句主要用來對一個或多個參數(shù)條目指定一個操作符,每個線程將創(chuàng)建參數(shù)條目的一個私有拷貝,在區(qū)域的結(jié)束處,將用私有拷貝的值通過指定的運行符運算,原始的參數(shù)條目被運算結(jié)果的值更新。 reduction子句用法如下: reduction(operator:list) 下表列出了可以用于reduction子句的一些操作符以及對應(yīng)私有拷貝變量缺省的初始值,私有拷貝變量的實際初始值依賴于redtucion變量的數(shù)據(jù)類型。 表10-4-1:reduction操作中各種操作符號對應(yīng)拷貝變量的缺省初始值
Operator Initialization value
+ 0
* 1
- 0
& ~0
| 0
^ 0
&& 1
|| 0
例如一個整數(shù)求和的程序如下: int i, sum = 100; #pragma omp parallel for reduction(+: sum) for ( i = 0; i < 1000; i++ ) { sum += i; } printf( "sum = %ld/n", sum); 注意,如果在并行區(qū)域內(nèi)不加鎖保護(hù)就直接對共享變量進(jìn)行寫操作,存在數(shù)據(jù)競爭問題,會導(dǎo)致不可預(yù)測的異常結(jié)果。共享數(shù)據(jù)作為privatefirstprivatelastprivatethreadprivatereduction子句的參數(shù)進(jìn)入并行區(qū)域后,就變成線程私有了,不需要加鎖保護(hù)了。 10.1.8?copyin子句 copyin子句用來將主線程中threadprivate變量的值拷貝到執(zhí)行并行區(qū)域的各個線程的threadprivate變量中,便于線程可以訪問主線程中的變量值, 用法如下: copyin(list) ? copyin中的參數(shù)必須被聲明成threadprivate的,對于類類型的變量,必須帶有明確的拷貝賦值操作符。 對于前面threadprivate中講過的計數(shù)器函數(shù),如果多個線程使用時,各個線程都需要對全局變量counter的副本進(jìn)行初始化,可以使用copyin子句來實現(xiàn),示例代碼如下: int main(int argc, char* argv[]) { int iterator; #pragma omp parallel sections copyin(counter) { #pragma omp section { int count1; for ( iterator = 0; iterator < 100; iterator++ ) { count1 = increment_counter(); } printf("count1 = %ld/n", count1); } #pragma omp section { int count2; for ( iterator = 0; iterator < 200; iterator++ ) { count2 = increment_counter(); } printf("count2 = %ld/n", count2); } } printf("counter = %ld/n", counter); } 打印結(jié)果如下: count1 = 100 count2 = 200 counter = 0 ? 從打印結(jié)果可以看出,兩個線程都正確實現(xiàn)了各自的計數(shù)。 10.1.9?copyprivate子句 copyprivate子句提供了一種機制用一個私有變量將一個值從一個線程廣播到執(zhí)行同一并行區(qū)域的其他線程。 用法如下: copyprivate(list) copyprivate子句可以關(guān)聯(lián)single構(gòu)造,在single構(gòu)造的barrier到達(dá)之前就完成了廣播工作。copyprivate可以對privatethreadprivate子句中的變量進(jìn)行操作,但是當(dāng)使用single構(gòu)造時,copyprivate的變量不能用于privatefirstprivate子句中。 下面便是一個使用copyprivate的代碼例子: int counter = 0; #pragma omp threadprivate(counter) int increment_counter() { counter++; return(counter); } #pragma omp parallel { int??? count; #pragma omp single copyprivate(counter) { counter = 50; } count = increment_counter(); printf("ThreadId: %ld, count = %ld/n", omp_get_thread_num(), count); } 打印結(jié)果為: ThreadId: 2, count = 51 ThreadId: 0, count = 51 ThreadId: 3, count = 51 ThreadId: 1, count = 51 如果沒有使用copyprivate子句,那么打印結(jié)果為: ThreadId: 2, count = 1 ThreadId: 1, count = 1 ThreadId: 0, count = 51 ThreadId: 3, count = 1 從打印結(jié)果可以看出,使用copyprivate子句后,single構(gòu)造內(nèi)給counter賦的值被廣播到了其他線程里,但沒有使用copyprivate子句時,只有一個線程獲得了single構(gòu)造內(nèi)的賦值,其他線程沒有獲取single構(gòu)造內(nèi)的賦值。 參考文獻(xiàn):

Ananth?Grama, Anshul Gupta,“并行計算導(dǎo)論”,張武等譯,機械工業(yè)出版社,2005.01

Michael J. Quinn, “MPI與OpenMP并行程序設(shè)計”,陳文光等譯,清華大學(xué)出版社,2004.10

Shameem Akhter等,“多核程序設(shè)計技術(shù)-通過軟件多線程提升性能”,電子工業(yè)出版社,2007.03 OpenMP2.5規(guī)范http://www.openmp.org/ OpenMP2.0規(guī)范http://www.openmp.org/ MSDN幫助材料ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_vclang/html/652414c5-78ed-4b7f-8283-1a9fe4c5e78d.htm?? 注:本文的寫作主要參考OpenMP2.5規(guī)范等參考文獻(xiàn),限于水平,可能存在對規(guī)范理解不足之處,請大家發(fā)現(xiàn)問題時不吝指正。

4、OpenMP中的任務(wù)調(diào)度????????????? (OpenMP線程同步)

OpenMP中的任務(wù)調(diào)度 OpenMP中,任務(wù)調(diào)度主要用于并行的for循環(huán)中,當(dāng)循環(huán)中每次迭代的計算量不相等時,如果簡單地給各個線程分配相同次數(shù)的迭代的話,會造成各個線程計算負(fù)載不均衡,這會使得有些線程先執(zhí)行完,有些后執(zhí)行完,造成某些CPU核空閑,影響程序性能。例如以下代碼: int i, j; int a[100][100] = {0}; for ( i =0; i < 100; i++) { for( j = i; j < 100; j++ ) { a[i][j] = i*j; } } 如果將最外層循環(huán)并行化的話,比如使用4個線程,如果給每個線程平均分配25次循環(huán)迭代計算的話,顯然i=0和i=99的計算量相差了100倍,那么各個線程間可能出現(xiàn)較大的負(fù)載不平衡情況。為了解決這些問題,OpenMP中提供了幾種對for循環(huán)并行化的任務(wù)調(diào)度方案。 在OpenMP中,對for循環(huán)并行化的任務(wù)調(diào)度使用schedule子句來實現(xiàn),下面介紹schedule字句的用法。 1.1.1??????Schedule子句用法 schedule子句的使用格式為: schedule(type[,size]) schedule有兩個參數(shù):type和size,size參數(shù)是可選的。 1.??????type參數(shù) 表示調(diào)度類型,有四種調(diào)度類型如下: ·?dynamic ·?guided ·?runtime ·?static 這四種調(diào)度類型實際上只有static、dynamic、guided三種調(diào)度方式,runtime實際上是根據(jù)環(huán)境變量來選擇前三種中的某中類型。 run-sched-var 2.??????size參數(shù) (可選) size參數(shù)表示循環(huán)迭代次數(shù),size參數(shù)必須是整數(shù)。static、dynamic、guided三種調(diào)度方式都可以使用size參數(shù),也可以不使用size參數(shù)。當(dāng)type參數(shù)類型為runtime時,size參數(shù)是非法的(不需要使用,如果使用的話編譯器會報錯)。 1.1.2??????靜態(tài)調(diào)度(static) 當(dāng)parallel for編譯指導(dǎo)語句沒有帶schedule子句時,大部分系統(tǒng)中默認(rèn)采用static調(diào)度方式,這種調(diào)度方式非常簡單。假設(shè)有n次循環(huán)迭代,t個線程,那么給每個線程靜態(tài)分配大約n/t次迭代計算。這里為什么說大約分配n/t次呢?因為n/t不一定是整數(shù),因此實際分配的迭代次數(shù)可能存在差1的情況,如果指定了size參數(shù)的話,那么可能相差一個size。 靜態(tài)調(diào)度時可以不使用size參數(shù),也可以使用size參數(shù)。 3.??????不使用size參數(shù) 不使用size參數(shù)時,分配給每個線程的是n/t次連續(xù)的迭代,不使用size參數(shù)的用法如下: schedule(static) 例如以下代碼: ??? #pragma omp parallelfor schedule(static) ??? for(i = 0; i < 10; i++ ) ??? { ???????? printf("i=%d, thread_id=%d/n", i, omp_get_thread_num()); } 上面代碼執(zhí)行時打印的結(jié)果如下: i=0, thread_id=0 i=1, thread_id=0 i=2, thread_id=0 i=3, thread_id=0 i=4, thread_id=0 i=5, thread_id=1 i=6, thread_id=1 i=7, thread_id=1 i=8, thread_id=1 i=9, thread_id=1 可以看出線程0得到了0~4次連續(xù)迭代,線程1得到5~9次連續(xù)迭代。注意由于多線程執(zhí)行時序的隨機性,每次執(zhí)行時打印的結(jié)果順序可能存在差別,后面的例子也一樣。 4.??????使用size參數(shù) 使用size參數(shù)時,分配給每個線程的size次連續(xù)的迭代計算,用法如下: schedule(static, size) 例如以下代碼: ??? #pragma omp parallelfor schedule(static, 2) ??? for(i = 0; i < 10; i++ ) ??? { ???????? printf("i=%d, thread_id=%d/n", i, omp_get_thread_num()); } 執(zhí)行時會打印以下結(jié)果: i=0, thread_id=0 i=1, thread_id=0 i=4, thread_id=0 i=5, thread_id=0 i=8, thread_id=0 i=9, thread_id=0 i=2, thread_id=1 i=3, thread_id=1 i=6, thread_id=1 i=7, thread_id=1 從打印結(jié)果可以看出,0、1次迭代分配給線程0,2、3次迭代分配給線程1,4、5次迭代分配給線程0,6、7次迭代分配給線程1,…。每個線程依次分配到2次連續(xù)的迭代計算。 1.1.3??????動態(tài)調(diào)度(dynamic) 動態(tài)調(diào)度是動態(tài)地將迭代分配到各個線程,動態(tài)調(diào)度可以使用size參數(shù)也可以不使用size參數(shù),不使用size參數(shù)時是將迭代逐個地分配到各個線程,使用size參數(shù)時,每次分配給線程的迭代次數(shù)為指定的size次。 下面為使用動態(tài)調(diào)度不帶size參數(shù)的例子: #pragma omp parallel for schedule(dynamic) for(i = 0; i < 10; i++ ) { printf("i=%d, thread_id=%d/n", i, omp_get_thread_num()); } 打印結(jié)果如下: i=0, thread_id=0 i=1, thread_id=1 i=2, thread_id=0 i=3, thread_id=1 i=5, thread_id=1 i=6, thread_id=1 i=7, thread_id=1 i=8, thread_id=1 i=4, thread_id=0 i=9, thread_id=1 下面為動態(tài)調(diào)度使用size參數(shù)的例子: #pragma omp parallel for schedule(dynamic, 2) for(i = 0; i < 10; i++ ) { printf("i=%d, thread_id=%d/n", i, omp_get_thread_num()); } 打印結(jié)果如下: i=0, thread_id=0 i=1, thread_id=0 i=4, thread_id=0 i=2, thread_id=1 i=5, thread_id=0 i=3, thread_id=1 i=6, thread_id=0 i=8, thread_id=1 i=7, thread_id=0 i=9, thread_id=1 從打印結(jié)果可以看出第0、1,4、5,6、7次迭代被分配給了線程0,第2、3,8、9次迭代則分配給了線程1,每次分配的迭代次數(shù)為2。 1.1.4??????guided調(diào)度(guided) guided調(diào)度是一種采用指導(dǎo)性的啟發(fā)式自調(diào)度方法。開始時每個線程會分配到較大的迭代塊,之后分配到的迭代塊會逐漸遞減。迭代塊的大小會按指數(shù)級下降到指定的size大小,如果沒有指定size參數(shù),那么迭代塊大小最小會降到1。 例如以下代碼: ??? #pragma omp parallelfor schedule(guided,2) ??? for(i = 0; i < 10; i++ ) ??? { ???????? printf("i=%d, thread_id=%d/n", i, omp_get_thread_num()); } 打印結(jié)果如下: i=0, thread_id=0 i=1, thread_id=0 i=2, thread_id=0 i=3, thread_id=0 i=4, thread_id=0 i=8, thread_id=0 i=9, thread_id=0 i=5, thread_id=1 i=6, thread_id=1 i=7, thread_id=1 第0、1、2、3、4次迭代被分配給線程0,第5、6、7次迭代被分配給線程1,第8、9次迭代被分配給線程0,分配的迭代次數(shù)呈遞減趨勢,最后一次遞減到2次。 1.1.5??????runtime調(diào)度(rumtime) runtime調(diào)度并不是和前面三種調(diào)度方式似的真實調(diào)度方式,它是在運行時根據(jù)環(huán)境變量OMP_SCHEDULE來確定調(diào)度類型,最終使用的調(diào)度類型仍然是上述三種調(diào)度方式中的某種。 例如在unix系統(tǒng)中,可以使用setenv命令來設(shè)置OMP_SCHEDULE環(huán)境變量: setenv OMP_SCHEDULE?“dynamic, 2” 上述命令設(shè)置調(diào)度類型為動態(tài)調(diào)度,動態(tài)調(diào)度的迭代次數(shù)為2。 在windows環(huán)境中,可以在”系統(tǒng)屬性|高級|環(huán)境變量”對話框中進(jìn)行設(shè)置環(huán)境變量。

5、OpenMP創(chuàng)建線程中的鎖及原子操作性能比較

OpenMP創(chuàng)建線程中的鎖及原子操作性能比較 相關(guān)文檔連接: 多核編程中的任務(wù)隨機競爭模式的概率分析 多核編程中的任務(wù)分組競爭模式???????????? ?多核編程中的負(fù)載平衡難題 多核編程中的鎖競爭難題 多核編程的幾個難題及其應(yīng)對策略(難題一) OpenMP并行程序設(shè)計(二) ?OpenMP并行程序設(shè)計(一) ?雙核CPU上的快速排序效率???????????? 在多核CPU中鎖競爭到底會造成性能怎樣的下降呢?相信這是許多人想了解的,因此特地寫了一個測試程序來測試原子操作,windows CriticalSection, OpenMP的鎖操作函數(shù)在多核CPU中的性能。 原子操作選用InterlockedIncrement來進(jìn)行測試, 對每種鎖和原子操作,都測試在單任務(wù)執(zhí)行和多任務(wù)執(zhí)行2000000次加鎖解鎖操作所消耗的時間。 測試的詳細(xì)代碼見后面。 測試機器環(huán)境:Intel 2.66G 雙核CPU 機器一臺 測試運行結(jié)果如下: SingleThread, InterlockedIncrement 2,000,000: a = 2000000, time = 78 MultiThread, InterlockedIncrement 2,000,000: a = 2000000, time = 156 SingleThread, Critical_Section 2,000,000:a = 2000000, time = 172 MultiThread, Critical_Section, 2,000,000:a = 2000000, time = 3156 SingleThread,omp_lock 2,000,000:a = 2000000, time = 250 MultiThread,omp_lock 2,000,000:a = 2000000, time = 1063 在單任務(wù)運行情況下,所消耗的時間如下: 原子操作?? ??????????????78ms Windows CriticalSection?172ms OpenMP的lock操作??????? 250ms 因此從單任務(wù)情況來看,原子操作最快,Windows CriticalSection次之,OpenMP庫帶的鎖最慢,但這幾種操作的時間差距不是很大,用鎖操作比原子操作慢了2~3倍左右。 在多個任務(wù)運行的情況下,所消耗的時間如下: 原子操作?? ??????????????156ms Windows CriticalSection?3156ms OpenMP的lock操作??????? 1063ms 在多任務(wù)運行情況下,情況發(fā)生了意想不到的變化,原子操作時間比單任務(wù)操作時慢了一倍,在兩個CPU上運行比在單個CPU上運行還慢一倍,真是難以想象,估計是任務(wù)切換開銷造成的。 Windows CriticalSection則更離譜了,居然花了3156ms,是單任務(wù)運行時的18倍多的時間,慢得簡直無法想象。 OpenMP的lock操作比Windows CriticalSection稍微好一些,但也花了1063ms,是單任務(wù)時的7倍左右。 由此可以知道,在多核CPU的多任務(wù)環(huán)境中,原子操作是最快的,而OpenMP次之,Windows CriticalSection則最慢。 同時從這些鎖在單任務(wù)和多任務(wù)下的性能差距可以看出,,多核CPU上的編程和以往的單核多任務(wù)編程會有很大的區(qū)別。 需要說明的是,本測試是一種極端情況下的測試,鎖住的操作只是一個簡單的加1操作,并且鎖競爭次數(shù)達(dá)200萬次之多,在實際情況中,一由于任務(wù)中還有很多不需要加鎖的代碼在運行,實際情況中的性能會比本測試的性能好很多。 測試代碼如下: // TestLock.cpp : OpenMP任務(wù)中的原子操作和鎖性能測試程序。 // #include<windows.h> #include<time.h> #include<process.h> #include<omp.h> #include<stdio.h> void TestAtomic() { ???? clock_t?t1,t2; ???? int????? i = 0; ???? volatile LONG????? a = 0; ???? t1 = clock(); ???? for( i = 0; i < 2000000; i++ ) ???? { ???????? InterlockedIncrement( &a); ???? } ???? ???? t2 = clock(); ???? printf("SingleThread, InterlockedIncrement 2,000,000: a = %ld, time = %ld/n", a, t2-t1); ???? t1 = clock(); #pragma omp parallelfor ???? for( i = 0; i < 2000000; i++ ) ???? { ???????? InterlockedIncrement( &a); ???? } ???? ???? t2 = clock(); ???? printf("MultiThread, InterlockedIncrement 2,000,000: a = %ld, time = %ld/n", a, t2-t1); } void TestOmpLock() { ???? clock_t t1,t2; ???? int i; ???? int a = 0; ???? omp_lock_t??? mylock; ???? omp_init_lock(&mylock); ???? t1 = clock(); ???? for( i = 0; i < 2000000; i++ ) ???? { ???????? omp_set_lock(&mylock); ???????? a+=1; ???????? omp_unset_lock(&mylock); ???? } ???? t2 = clock(); ???? ???? printf("SingleThread,omp_lock 2,000,000:a = %ld, time = %ld/n", a, t2-t1); ???? t1 = clock(); #pragma omp parallelfor ???? for( i = 0; i < 2000000; i++ ) ???? { ???????? omp_set_lock(&mylock); ???????? a+=1; ???????? omp_unset_lock(&mylock); ???? } ???? t2 = clock(); ???? ???? printf("MultiThread,omp_lock 2,000,000:a = %ld, time = %ld/n", a, t2-t1); ???? omp_destroy_lock(&mylock); } void TestCriticalSection() { ???? clock_t t1,t2; ???? int i; ???? int?a = 0; ???? CRITICAL_SECTION?? cs; ???? InitializeCriticalSection(&cs); ???? t1 = clock(); ???? for( i = 0; i < 2000000; i++ ) ???? { ???????? EnterCriticalSection(&cs); ???????? a+=1; ???????? LeaveCriticalSection(&cs); ???? } ???? t2 = clock(); ???? printf("SingleThread, Critical_Section 2,000,000:a = %ld, time = %ld/n", a, t2-t1); ???? t1 = clock(); #pragma omp parallelfor ???? for( i = 0; i < 2000000; i++ ) ???? { ???????? EnterCriticalSection(&cs); ???????? a+=1; ???????? LeaveCriticalSection(&cs); ???? } ???? t2 = clock(); ???? printf("MultiThread, Critical_Section, 2,000,000:a = %ld, time = %ld/n", a, t2-t1); ???? DeleteCriticalSection(&cs); } int main(int argc,char* argv[]) { ???? TestAtomic(); ???? TestCriticalSection(); ???? TestOmpLock(); ???? return 0; }

6、OpenMP程序設(shè)計的兩個小技巧

???????OpenMP程序設(shè)計的兩個小技巧

1、動態(tài)設(shè)置并行循環(huán)的線程數(shù)量 在實際情況中,程序可能運行在不同的機器環(huán)境里,有些機器是雙核,有些機器是4核甚至更多核。并且未來硬件存在升級的可能,CPU核數(shù)會變得越來越多。如何根據(jù)機器硬件的不同來自動設(shè)置合適的線程數(shù)量就顯得很重要了,否則硬件升級后程序就得進(jìn)行修改,那將是一件很麻煩的事情。 比如剛開始在雙核系統(tǒng)中開發(fā)的軟件,線程數(shù)量缺省都設(shè)成2,那么當(dāng)機器升級到4核或8核以后,線程數(shù)量就不能滿足要求了,除非修改程序。 線程數(shù)量的設(shè)置除了要滿足機器硬件升級的可擴(kuò)展性外,還需要考慮程序的可擴(kuò)展性,當(dāng)程序運算量增加或減少后,設(shè)置的線程數(shù)量仍然能夠滿足要求。顯然這也不能通過設(shè)置靜態(tài)的線程數(shù)量來解決。 在具體計算需要使用多少線程時,主要需要考慮以下兩點: 1)?????????????當(dāng)循環(huán)次數(shù)比較少時,如果分成過多數(shù)量的線程來執(zhí)行,可能會使得總運行時間高于較少線程或一個線程執(zhí)行的情況。并且會增加能耗。 2)?????????????如果設(shè)置的線程數(shù)量遠(yuǎn)大于CPU核數(shù)的話,那么存在著大量的任務(wù)切換和調(diào)度等開銷,也會降低整體效率。 那么如何根據(jù)循環(huán)的次數(shù)和CPU核數(shù)來動態(tài)地設(shè)置線程的數(shù)量呢?下面以一個例子來說明動態(tài)設(shè)置線程數(shù)量的算法,假設(shè)一個需要動態(tài)設(shè)置線程數(shù)的需求為: 1、?以多個線程運行時的每個線程運行的循環(huán)次數(shù)不低于4次 2、?總的運行線程數(shù)最大不超過2倍CPU核數(shù) 下面代碼便是一個實現(xiàn)上述需求的動態(tài)設(shè)置線程數(shù)量的例子 ?? constint MIN_ITERATOR_NUM = 4; ?? int ncore = omp_get_num_procs();//獲取執(zhí)行核的數(shù)量 ?? int max_tn = n / MIN_ITERATOR_NUM; ?? int tn = max_tn > 2*ncore ? 2*ncore : max_tn;?//tn表示要設(shè)置的線程數(shù)量 #pragma omp parallelforif( tn > 1) num_threads(tn) ???? for ( i = 0; i < n; i++ ) ???? { ???????? printf("Thread Id = %ld/n", omp_get_thread_num()); ???????? //Do some work here ???? } 在上面代碼中,根據(jù)每個線程運行的循環(huán)次數(shù)不低于4次,先計算出最大可能的線程數(shù)max_tn,然后計算需要的線程數(shù)量tn,tn的值等于max_tn和2倍CPU核數(shù)中的較小值。 然后在parallel for構(gòu)造中使用if子句來判斷tn是否大于1,大于1時使用單個線程,否則使用tn個線程,,這樣就使得設(shè)置的線程數(shù)量滿足了需求中的條件。 比如在一個雙核CPU上,n=64,最終會以2倍CPU核數(shù)(4個)線程運行,而不會以max_tn = 64/4=16個線程運行。 在實際情況中,當(dāng)然不能每個循環(huán)都象上面一樣寫幾行代碼來計算一遍,可以將其寫成一個獨立的功能函數(shù)如下: const int g_ncore = omp_get_num_procs(); //獲取執(zhí)行核的數(shù)量 /**?計算循環(huán)迭代需要的線程數(shù)量 ???? 根據(jù)循環(huán)迭代次數(shù)和CPU核數(shù)及一個線程最少需要的循環(huán)迭代次數(shù) ???? 來計算出需要的線程數(shù)量,計算出的最大線程數(shù)量不超過CPU核數(shù) ???? @param?? int n - 循環(huán)迭代次數(shù)?? ???? @param?? int min_n - 單個線程需要的最少迭代次數(shù)??? ???? @return?int - 線程數(shù)量???? */ int dtn(int n, int min_n) { ?? int max_tn = n / min_n; ?? int tn = max_tn > g_ncore ? g_ncore : max_tn;?//tn表示要設(shè)置的線程數(shù)量 ?? if ( tn < 1 ) ?? { ???? ?? tn = 1; ?? } ?? return tn; } 這樣每次并行化循環(huán)時就可以直接使用函數(shù)dtn()來獲取合適的線程數(shù)量,前面的代碼可以簡寫成如下形式: #pragma omp parallelfor num_threads(dtn(n, MIN_ITERATOR_NUM)) ???? for ( i = 0; i < n; i++ ) ???? { ???????? printf("Thread Id = %ld/n", omp_get_thread_num()); ???????? //Do some work here ???? } 當(dāng)然具體設(shè)置多少線程要視情況而定的,一般情況下線程數(shù)量剛好等于CPU核數(shù)可以取得比較好的性能,因為線程數(shù)等于CPU核數(shù)時,每個核執(zhí)行一個任務(wù),沒有任務(wù)切換開銷。 2、嵌套循環(huán)的并行化 在嵌套循環(huán)中,如果外層循環(huán)迭代次數(shù)較少時,如果將來CPU核數(shù)增加到一定程度時,創(chuàng)建的線程數(shù)將可能小于CPU核數(shù)。另外如果內(nèi)層循環(huán)存在負(fù)載平衡的情況下,很難調(diào)度外層循環(huán)使之達(dá)到負(fù)載平衡。 下面以矩陣乘法作為例子來講述如何將嵌套循環(huán)并行化,以滿足上述擴(kuò)展性和負(fù)載平衡需求。 一個串行的矩陣乘法的函數(shù)代碼如下: /**?矩陣串行乘法函數(shù) ??? @param??? int *a - 指向要相乘的第個矩陣的指針 ??? @param??? int row_a - 矩陣a的行數(shù) ??? @param??? int col_a - 矩陣a的列數(shù) ??? @param??? int *b - 指向要相乘的第個矩陣的指針? ??? @param??? int row_b - 矩陣b的行數(shù) ??? @param??? int col_b - 矩陣b的列數(shù) ??? @param??? int *c - 計算結(jié)果的矩陣的指針 ??? @param??? int c_size - 矩陣c的空間大小(總元素個數(shù)) ??? @return?? void - */ void Matrix_Multiply(int *a, int row_a, int col_a, ????????????????????? int *b, int row_b,int col_b, ????????????????????? int *c, int c_size) { ??? if ( col_a != row_b || c_size < row_a * col_b ) ??? { ??????? return; ??? } ??? int i, j, k; //#pragma omp for private(i, j, k) ??? for ( i = 0; i < row_a; i++ ) ??? { ??????? int row_i = i * col_a; ??????? int row_c = i * col_b; ??????? for ( j = 0; j < col_b; j++ ) ??????? { ???????????c[row_c + j] = 0; ??????????? for ( k = 0; k < row_b; k++ ) ??????????? { ??????????????? c[row_c + j] += a[row_i + k] * b[k * col_b + j]; ??????????? } ??????? } ??? } } 如果在外層循環(huán)前加上OpenMP的for語句時,它就變成了一個并行的矩陣乘法函數(shù),但是這樣簡單地將其并行化顯然無法滿足前面所述的擴(kuò)展性需求。 其實可以采用一個簡單的方法將最外層循環(huán)和第2層循環(huán)合并成一個循環(huán),下面便是采用合并循環(huán)后的并行實現(xiàn)。 void Parallel_Matrix_Multiply(int *a, int row_a, int col_a, ???????????????????? int *b, int row_b,int col_b, ???????????????????? int *c, int c_size ) { ??? if ( col_a != row_b ) ??? { ??????? return; ??? } ??? int i, j, k; ??? int index; ??? int border = row_a * col_b; ??? i = 0; ??? j = 0; #pragma omp parallel private(i,j,k) num_threads(dtn(border, 1)) ??? for ( index = 0; index < border; index++ ) ??? { ??????? i = index / col_b; ??????? j = index % col_b; ??????? int row_i = i * col_a; ??????? int row_c = i * col_b; ??????? c[row_c+j] = 0; ??????? for ( k = 0; k < row_b; k++ ) ??????? { ??????????? c[row_c + j] += a[row_i+k] * b[k*col_b+j]; ??????? } ??? } } 從上面代碼可以看出,合并后的循環(huán)邊界border = row_a * col_b;即等于原來兩個循環(huán)邊界之積,然后在循環(huán)中計算出原來的外層循環(huán)和第2層循環(huán)的迭代變量i和j,采用除法和取余來求出i和j的值。 需要注意的是,上面求i和j的值必須要保證循環(huán)迭代的獨立性,即不能有循環(huán)迭代間的依賴關(guān)系。不能將求i和j值的過程優(yōu)化成如下的形式: if ( j == col_b ) { ???? j = 0; ???? i++; } // …… 此處代表實際的矩陣乘法代碼 j++; 上面這種優(yōu)化,省去了除法,效率高,但是只能在串行代碼中使用,因為它存在循環(huán)迭代間的依賴關(guān)系,無法將其正確地并行化。


??????? 上面列出的這些OpenMP知識,屬于初步的入門知識,如果需要進(jìn)一步深入掌握OpenMP或者了解其實現(xiàn)原理,則需要看更多的參考文獻(xiàn)。下面列出我寫的《多核計算與程序設(shè)計》一書的第3章OpenMP程序設(shè)計中的參考文獻(xiàn),供需要深入掌握的人參考。其中的文獻(xiàn)【2】講解了OpenMP的實現(xiàn)原理。

【1】 Ananth Grama, Anshul Gupta,“并行計算導(dǎo)論”,張武等譯,機械工業(yè)出版社,2005.01

【2】 Barbara Chapman, “How OpenMP is Compiled ”,http://cobweb.ecn.purdue.edu/ParaMount/iwomp2008/documents/chapman-underthehood

【3】 Bruce McMillin等,“Parallel Algorithm Fundamentals and Analysis”,http://citeseer.ist.psu.edu/mcmillin93parallel.html

【4】 Common Language Infrastructure (CLI) Partitions I to VIhttp://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf

【5】 Introduction to OpenMP,A Directive-based API for Parallel Processing on Shared-memory Computers,http://scv.bu.edu/documentation/tutorials/OpenMP/

【6】 Michael J. Quinn, “MPI與OpenMP并行程序設(shè)計”,陳文光等譯,清華大學(xué)出版社,2004.10

【7】 Mitsuhisa Sato, Shigehisa Satoh, Kazuhiro Kusano and Yoshio Tanaka, “Design of OpenMP Compiler for an SMP Cluster”,

http://www.hpcs.is.tsukuba.ac.jp/~msato/pdplab/papers/ewomp99.pdf

【8】 MSDN幫助材料

ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_vclang/html/652414c5-78ed-4b7f-8283-1a9fe4c5e78d.htm

【9】 Omni OpenMP compiler, http://phase.hpcc.jp/omni/home.html.

【10】 OpenMP2.0規(guī)范http://www.openmp.org/

【11】 OpenMP2.5規(guī)范http://www.openmp.org/

【12】 OpenMP: Simple, portable, scalable SMP Programming, http://www.OpenMP.org.

【13】 Rudolf Eigenmann and Timothy G. mattson. “OpenMP tutorial, part 2: Advanced OpenMP.”,http://www.cise.ufl.edu/research/ParallelPatterns/sc01-omp-tut-advanced.ppt.

【14】 Ruud van der Pas ,“An Introduction Into OpenMP”,http://www.nic.uoregon.edu/iwomp2005/iwomp2005_tutorial_openmp_rvdp.pdf

【15】 Sanjiv Shah, Grant Haab, Paul Petersen, & Joe Throop,“Flexible Control Structures for Parallelism in OpenMP”,http://www.it.lth.se/ewomp99/papers/grant.pdf

【16】 Shameem Akhter等,“多核程序設(shè)計技術(shù)-通過軟件多線程提升性能”,電子工業(yè)出版社,2007.03

【17】 Special issue on OpenMP and its applications. Scientific Programming, 11(2),2003.

【18】 Y. Charlie Hu, Honghui Lu, Alan L. Cox, and Willy Zwaenepoel.OpenMP for networks of SMPs”,In Proceedings of 13th International Parallel Processing Symposium and 10th Symposium on Parallel and Distributed Processing, page 302-310. IEEE Computer Society, 1999.

多核編程相關(guān)文章:

1)用原子操作解決多線程退出問題

2)原子操作在多核編程中的使用

3)多核編程偽共享問題及其對策

4)多核編程鎖競爭問題及其對策

5)多核分布式隊列的實現(xiàn):“偷”與“自私”的運用

6)多核編程中的條件同步模式

7)多核編程的四層境界

8)“老子”是偉大的多核計算科學(xué)家

9)程序員的十層樓(1~3層)

10)多核編程文章匯總

11)并行順序搜索及終止檢測

《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的OpenMP: OpenMP编程指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。