OpenMP与C++ 事半功倍地获得多线程的好处 下
聲明:本文并未獲得翻譯授權(quán),本人翻譯這篇文章僅用于學(xué)習(xí)和研究之用,任何人不得在未經(jīng)授權(quán)之前將原文和譯文用以商業(yè)用途.
因版權(quán)原因,暫不建議轉(zhuǎn)載本文.
本文發(fā)表于http://blog.csdn.net/lanphaday
請保留本文完整
本文發(fā)表于2005年第10期的<MSDN Mag>,英文版本地址:
http://msdn.microsoft.com/msdnmag/issues/05/10/OpenMP/default.aspx
?
OpenMP與C++:事半功倍地獲得多線程的好處(下)Kang Su Gatlin & Pete Isensee 著賴勇浩 譯(續(xù)上)上篇:http://blog.csdn.net/lanphaday/archive/2007/02/06/1503817.aspx用以同步的編譯器指令
?????? 在多個線程并發(fā)的時候,某一線程常常會需要同步其它線程。OpenMP支持多種類型的同步,以在不同的情境下解決問題。
?????? 其中之一就是暗含的barrier同步。在每一個并行區(qū)域都有一個暗含的barrier,用以同步并行區(qū)域中的所有線程。一個barrier同步要求所有線程執(zhí)行到此,然后才能往下執(zhí)行。
?????? #pragma omp for,#pragma omp single和#pragma omp sections程序塊都有暗含的barrier同步。從上述三種工作共享的程序塊中去除暗含的barrier同步的方法是增加nowait子句:
?????? #pragma omp parallel
{
??? #pragma omp for nowait
??? for(int i = 1; i < size; ++i)
??????? x[i] = (y[i-1] + y[i+1])/2;
}
如你所見,工作共享指令中的nowait子句指明線程不需要在for循環(huán)結(jié)束時同步,盡管線程將在并行區(qū)域結(jié)束處同步。
?????? 另一種是明確聲明barrier同步,在一些情境下你可能需要在并行區(qū)域出口之外放置barrier同步,這時你可以在代碼里加一個#pragma omp barrier指令。
?????? 臨界區(qū)能夠像barrier那樣使用,在Win32 API中通過EnterCriticalSection和ExitCriticleSection來進出臨界區(qū)。OpenMP通過#pragma omp critical [name]指令給予程序員同樣的能力。這與Win32臨界區(qū)有同樣的語義,并且隱藏了EnterCriticleSection的調(diào)用。你可以使用命名的臨界區(qū),這種情況下代碼段僅與同名臨界區(qū)互斥。如果沒有指定臨界區(qū)名字,則映射到用戶未定義的名字。這些未命名的臨界區(qū)與區(qū)域相關(guān)的每一臨界區(qū)互斥。
?????? 在一個并行區(qū)域里,經(jīng)常限制同時只有一條線程能夠訪問一段代碼,例如在并行區(qū)域的中間寫文件。大多數(shù)這種情況下,并不關(guān)心哪一條線程執(zhí)行這段代碼,只要只有一條線程執(zhí)行這段代碼即可,OpenMP用#pragma omp single指令來完成這個工作。
?????? 有此時候用single指令聲明必須由單一線程執(zhí)行并行區(qū)域中的一段代碼并不滿足需要。有些情況下你希望確保主線程來執(zhí)行這段代碼——例如主線程是GUI線程并且你希望GUI線程完成一些工作。#pragma omp master指令可以做到這一點。不像single,在進出一個master代碼塊的時候并沒有暗含的barrier。
?????? 內(nèi)存界定(Memory Fence)可用#pragma omp flush實現(xiàn),這條指令在程序中生成內(nèi)存界定,它的本質(zhì)上等效于_ReadWriteBarrier。
?????? 切記OpenMP指令同時影響線程組里的所有線程。因此下面的代碼片段是非法的并且有未定義的運行時行為(崩潰或者在特別情況下被掛起):
#pragma omp parallel
{
??? if(omp_get_thread_num() > 3)
??? {
??????? #pragma omp single??? // May not be accessed by all threads
??????? x++;
??? }
}
?
執(zhí)行環(huán)境例程
?????? 除了前文討論的編譯器指令OpenMP也包含一系列極為有用的運行時例程,用以編寫OpenMP應(yīng)用程序。有三大類型的例程可用:執(zhí)行環(huán)境例程,鎖/同步例程和定時例程(定時例程不在本文討論)。所有的OpenMP例程都在omp.h頭文件中定義并皆以omp_開頭。
?????? 運行時環(huán)境例程提供允許你查詢和設(shè)置OpenMP環(huán)境的各個方面的功能。以omp_set_開頭的函數(shù)只能在并行區(qū)域外調(diào)用,其它函數(shù)可在并行和非并行區(qū)域使用。
?????? 可以用omp_get_num_threads和omp_set_num_threads來讀取或者設(shè)置線程組的線程數(shù)量。omp_get_num_threads返回當(dāng)前線程組的線程數(shù)目。如果調(diào)用此函數(shù)的線程不在并行區(qū)域,返回1。omp_set_num_threads用以設(shè)置當(dāng)前線程執(zhí)行下一個并行區(qū)域的線程數(shù)。
?????? 但這并非設(shè)置線程數(shù)目的全部,并行區(qū)域的線程數(shù)目同樣依賴于OpenMP的另兩方面的配置環(huán)境:動態(tài)線程和嵌套。
?????? 動態(tài)線程是一個默認(rèn)為不使能的布爾屬性。當(dāng)線程將執(zhí)行一塊并行區(qū)域的時候如果這個屬性為不使能,那么OpenMP就生線程數(shù)量為omp_get_max_threads返回值的線程組。omp_get_max_threads默認(rèn)為計算機的硬件線程數(shù)或者環(huán)境變量OMP_NUM_THREADS的值。如果使能動態(tài)線程OpenMP將生成一個線程數(shù)量可變的線程組,但這個數(shù)量不會超過omp_get_max_threads的返回值。
?????? 嵌套是另一個默認(rèn)為不使能的布爾屬性。并行區(qū)域嵌套出現(xiàn)在當(dāng)線程已經(jīng)運行在并行區(qū)域又遇到另一個并行區(qū)域的時候。如果嵌套被使能,那么就按前文關(guān)于動態(tài)線程的規(guī)則生成一個新的線程組。相反地,線程組就只有單獨一個線程。
?????? 可以通過omp_set_dynamic、omp_get_dynamic、omp_set_nested和 omp_get_nested來設(shè)置或者查詢動態(tài)線程和嵌套的使能狀態(tài)。每一條線程都可以查詢它所處的環(huán)境。線程可以通過調(diào)用omp_get_thread_num來獲得它所處的線程組的線程數(shù)目——一個比調(diào)用omp_get_num_threads的返回值少0或者1的值。
?????? omp_in_parallel用以查詢本線程是否正在并行區(qū)域執(zhí)行。omp_get_num_proc用以獲知計算機有多少個CPU。
???????| ??????????? #include <stdio.h> ???????????#include <omp.h> ???????????? ???????????int main() ????????????? { ????????????? omp_set_dynamic(1); ????????????? omp_set_num_threads(10); ????????????? #pragma omp parallel??????? // parallel region 1 ????????????? { ???????????????? #pragma omp single ???????????????? printf("Num threads in dynamic region is = %d/n", ???????????????? omp_get_num_threads()); ????????????? } ????????????? printf("/n"); ????????????? omp_set_dynamic(0); ????????????? omp_set_num_threads(10); ????????????? #pragma omp parallel??????? // parallel region 2 ????????????? { ???????????????? #pragma omp single ????????????? ???printf("Num threads in non-dynamic region is = %d/n", ???????????????? omp_get_num_threads()); ????????????? } ????????????? printf("/n"); ????????????? omp_set_dynamic(1); ????????????? omp_set_num_threads(10); ????????????? #pragma omp parallel??????? // parallel region 3 ????????????? { ???????????????? #pragma omp parallel ???????????????? { ???????????? ???????#pragma omp single ??????????????????? printf("Num threads in nesting disabled region is = %d/n", ???????????????????? omp_get_num_threads()); ???????????????? } ????????????? } ????????????? printf("/n"); ????????????? omp_set_nested(1); ????????????? #pragma omp parallel??????? // parallel region 4 ????????????? { ???????????????? #pragma omp parallel ???????????????? { ??????????????????? #pragma omp single ??????????????????? printf("Num threads in nested region is = %d/n", ???????????????????? omp_get_num_threads()); ???????????????? } ????????????? } ???????????} ??????????? |
(圖6)使用OpenMP例程
?????? 圖6可以幫助你更清晰地理解這些不同的互相作用的環(huán)境例程。在這個例子中有4個截然不同的并行區(qū)域,包括兩個嵌套并行區(qū)域。
?????? 用Visual Studio 2005編譯之后在雙處理器的計算機上執(zhí)行上例,輸出如下:
Num threads in dynamic region is = 2
?
Num threads in non-dynamic region is = 10
?
Num threads in nesting disabled region is = 1
Num threads in nesting disabled region is = 1
?
Num threads in nested region is = 2
Num threads in nested region is = 2
在第一個并行區(qū)域使能了動態(tài)線程并設(shè)置線程數(shù)為10。從程序的輸出可以看到使能了動態(tài)線程的OpenMP在運行時僅為線程組分派兩條線程——因為計算機只有兩個處理器。在第二個并行區(qū)域,未使能動態(tài)線程的OpenMP為線程組分派了10條線程。
?????? 在第三、四個并行區(qū)域,你可以看到使能和未使能嵌套的影響。在第三個并行區(qū)域,因為沒有使能嵌套,所以沒有為嵌套的并行區(qū)域分派新的線程。因此嵌套和外部并行區(qū)域加起來只有兩條線程。在使能了嵌套的第四個并行區(qū)域中為嵌套并行區(qū)域生成了一個擁有兩條線程的線程組(故在嵌套并行區(qū)域總計有四條線程)。這種為每一個嵌套并行區(qū)域加倍增加線程的處理能夠一直進行下去,直到用完棧空間。實際上你可以生成幾百條線程,但這樣做的話開銷將會遠大于使用多線程獲得的性能優(yōu)勢。
?????? 可能你已經(jīng)留意到在第三、四并行區(qū)域中是使能了動態(tài)線程的,那么下面這段未使能動態(tài)線程的代碼又會有什么樣的執(zhí)行結(jié)果?
omp_set_dynamic(0);
omp_set_nested(1);
omp_set_num_threads(10);
#pragma omp parallel
{
??? #pragma omp parallel
??? {
??????? #pragma omp single
??????? printf("Num threads in nested region is = %d/n",
??????? omp_get_num_threads());
??? }
}
下面你可以看到預(yù)期的結(jié)果。在第一個并行區(qū)域開始處由一個10個線程的線程組執(zhí)行,后來并發(fā)的嵌套并行區(qū)域則為10個線程中的每一個線程分派有10個線程的線程組來執(zhí)行內(nèi)部并行區(qū)域。因此在嵌套并行區(qū)域內(nèi)部總計有100條線程執(zhí)行。
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
Num threads in nested region is = 10
?
同步與鎖
?????? OpenMP內(nèi)含用以幫助代碼同步的運行時例程;且內(nèi)含兩種類型的鎖——簡單的和可嵌套的,每一種都可以有三種狀態(tài)——未初始化、已上鎖和未上鎖。
?????? 簡單鎖(omp_lock_t)不可以多次上鎖,即使是同一線程也不允許。除了當(dāng)線程嘗試給已經(jīng)持有的鎖上鎖時不會阻塞外,可嵌套鎖(omp_nest_lock_t)與簡單鎖沒有不同。另外,可嵌套鎖使用引用計數(shù)并且知道已經(jīng)被上鎖了幾次。
?????? 同步例程能夠作用于鎖,每一個例程都有簡單鎖和可嵌套鎖變量。可以對鎖實行以下五個操作:initialize(初始化)、set(上鎖)、unset(解鎖)、test(測試)和destory(銷毀)。這些與Win32臨界區(qū)例程非常相似——事實上OpenMP就是通過在它們上層進行簡單封裝來實現(xiàn)的。圖7展示了OpenMP例程與Win32例程的對應(yīng)關(guān)系。
??????????? ??????????? ??????? ??????????? ??????????? ??????? ??????????? ??????????? ??????? ??????????? ??????????? ??????? ??????????? ??????????? ??????? ??????????? ??????????? ??????? ??????????? ??????????? ???????| ??????????? OpenMP Simple Lock ??????????? | ??????????? OpenMP Nested Lock ??????????? | ??????????? Win32 Routine ??????????? |
| ??????????? omp_lock_t ??????????? | ??????????? omp_nest_lock_t ??????????? | ??????????? CRITICAL_SECTION ??????????? |
| ??????????? omp_init_lock ??????????? | ??????????? omp_init_nest_lock ??????????? | ??????????? InitializeCriticalSection ??????????? |
| ??????????? omp_destroy_lock ??????????? | ??????????? omp_destroy_nest_lock ??????????? | ??????????? DeleteCriticalSection ??????????? |
| ??????????? omp_set_lock ??????????? | ??????????? omp_set_nest_lock ??????????? | ??????????? EnterCriticalSection ??????????? |
| ??????????? omp_unset_lock ??????????? | ??????????? omp_unset_nest_lock ??????????? | ??????????? LeaveCriticalSection ??????????? |
| ??????????? omp_test_lock ??????????? | ??????????? omp_test_nest_lock ??????????? | ??????????? TryEnterCriticalSection ??????????? |
(圖7)OpenMP與Win32的鎖例程對比
?????? 開發(fā)人員能夠在同步例程和同步編譯器指令之間任選其一。編譯器指令的優(yōu)勢是它們非常結(jié)構(gòu)化,這讓它們變得易懂并且容易從程序上測定你的同步區(qū)域的入口與出口。
?????? 同步例程的優(yōu)勢是它們的伸縮性。你能把鎖通過參數(shù)傳遞給函數(shù),在函數(shù)中這個鎖可以被上鎖或者解鎖。這是編譯器指令無法做到的。通常情況下應(yīng)該選擇使用編譯器指令,除非你需要只有使用運行時例程才能夠得到的伸縮性。
?
數(shù)據(jù)結(jié)構(gòu)遍歷并行化
?????? 圖8展示了兩個并行執(zhí)行迭代次數(shù)未知的for循環(huán)的例子,第一個例子是遍歷一個STL的std::vector窗口,另一個是標(biāo)準(zhǔn)鏈表。
???????| ??????????? #pragma omp parallel ???????????{ ????????????? // Traversing an STL vector in parallel ????????????? std::vector<int>::iterator iter; ????????????? for(iter = xVect.begin(); iter != xVect.end(); ++iter) ????????????? { ???????????????? #pragma omp single nowait ???????????????? { ??????????????????? process1(*iter); ???????????????? } ????????????? } ???????????? ????????????? // Walking a standard linked-list in parallel ????????????? for(LList *listWalk = listHead; listWalk != NULL; ???????????????????? listWalk = listWalk->next) ????????????? { ???????????????? #pragma omp single nowait ???????????????? { ??????????????????? process2(listWalk); ???????????????? } ????????????? } ???????????} ??????????? |
(圖8)處理可變次數(shù)的循環(huán)迭代
?????? 在例子的STL部分,線程組的每一條線程都執(zhí)行for循環(huán)并且擁有自己的迭代器拷貝。但每一次迭代時都只有一條線程進入循環(huán)體里的single代碼塊(語義上的single)。在運行時OpenMP執(zhí)行了用以確保single塊被且僅被執(zhí)行一次“魔法”。這種方式的迭代的開銷是巨大的,因此只有處理函數(shù)要做很多工作的時候才值得使用這種方式。鏈表的例子也是一樣的邏輯,就不多言了。
?????? 值得一提的是STL的std::vector那個例子里我們可以在需要進入循環(huán)之前用std::vector::size測定迭代次數(shù),這樣我們就可以重寫代碼為OpenMP規(guī)范的for循環(huán)形式,如下面的代碼所示:
#pragma omp parallel for
?? for(int i = 0; i < xVect.size(); ++i)
????? process(xVect[i]);
因為這種方式的運行時開銷要小得多,所以我們建議在數(shù)組、vector和其它任何能夠使用OpenMP規(guī)范的for循環(huán)遍歷的時候使用這一方式。
?
高級調(diào)度算法
?????? 默認(rèn)情況下,OpenMP在并行化for循環(huán)的時候使用一個名為靜態(tài)調(diào)度的線程調(diào)度算法,這一算法使得線程組的每一條線程獲得同樣多的迭代次數(shù);如果有n次迭代和T條線程,那每一條線程就得到n/T次迭代(OpenMP可以正確處理n不被T整除的情況)。但是OpenMP也提供了一些其它的調(diào)度機制以適應(yīng)不同的需要:動態(tài)調(diào)度、運行時調(diào)度和導(dǎo)向(guided)調(diào)度。
?????? 指定其它調(diào)度的方法是使用#pragma omp for或者#pragma omp parallel指令的schedule子句。這個子句的格式如下:
?????? schedule(schedule-algorithm[, chunk-size])
下面是一些例子:
#pragma omp parallel for schedule(dynamic, 15)
for(int i = 0; i < 100; ++i)
{ ...
#pragma omp parallel
?? #pragma omp for schedule(guided)
?????? 動態(tài)調(diào)度讓每一條線程執(zhí)行通過塊大小(chunk-size)(默認(rèn)為1)指定數(shù)量的迭代。當(dāng)線程執(zhí)行完交給它的迭代,它就請求再次執(zhí)行chunk-size次迭代,直到所有迭代結(jié)束。顯而易見,最后一次迭代可能少于chunk-size次。
?????? 導(dǎo)向調(diào)度是讓每一條線程執(zhí)行的迭代次數(shù)與線程數(shù)成比例:
iterations_to_do = max(iterations_not_assigned/omp_get_num_threads(), chunk-size)
當(dāng)線程執(zhí)行完交給它的迭代任務(wù),它請求基于iterations_to_do這一公式的數(shù)量的迭代。因此交給線程的迭代次數(shù)遞減,最后一次迭代調(diào)度的次數(shù)可能少于tierations_to_do函數(shù)定義的值。
?????? 下面是使用#pragma omp for schedule(dynamic, 15)指令調(diào)度4條線程處理100次迭代的過程:
Thread 0 gets iterations 1-15
Thread 1 gets iterations 16-30
Thread 2 gets iterations 31-45
Thread 3 gets iterations 46-60
Thread 2 finishes
Thread 2 gets iterations 61-75
Thread 3 finishes
Thread 3 gets iterations 76-90
Thread 0 finishes
Thread 0 gets iterations 91-100
接下來是使用#pragma omp for schedule(guided, 15)指令調(diào)度4條線程處理100次迭代的過程:
Thread 0 gets iterations 1-25
Thread 1 gets iterations 26-44
Thread 2 gets iterations 45-59
Thread 3 gets iterations 60-64
Thread 2 finishes
Thread 2 gets iterations 65-79
Thread 3 finishes
Thread 3 gets iterations 80-94
Thread 2 finishes
Thread 2 gets iterations 95-100
動態(tài)調(diào)度和導(dǎo)向調(diào)度是當(dāng)每一次迭代的工作量不盡相同時或者處理器的速度快慢不一時完美的調(diào)度機制。使用靜態(tài)調(diào)度是無法達到這樣的迭代負(fù)載平衡的。動態(tài)和導(dǎo)向調(diào)度通過它們非常自然的工作自動地平衡迭代負(fù)載。特別地,導(dǎo)向調(diào)度由于更少的調(diào)度開銷而比動態(tài)調(diào)度有更好性能。
最后要討論的是運行時調(diào)度,確切來說它并不是調(diào)度算法,但有時是上文提及的三種調(diào)度算法之外更好的選擇。當(dāng)通過schedule子句指定runtime方式,OpenMP在運行時就為當(dāng)前for循環(huán)使用通過OMP_SCHEDULE環(huán)境變量指定的調(diào)度方法。OMP_SCHEDULE環(huán)境變量的格式是type,[chunk-size]。例如:
set OMP_SCHEDULE=dynamic,8
使用運行時調(diào)度可以帶給終端用戶當(dāng)默認(rèn)為靜態(tài)調(diào)度時可以選擇調(diào)度算法的伸縮性。
?
應(yīng)用OpenMP的時機
?????? 知道什么時候應(yīng)用OpenMP與懂得如何使用OpenMP同樣重要。一般而言,下面的幾條指導(dǎo)方針可以幫助你做出決定:
目標(biāo)平臺是多核或者多處理器平臺。在這種情況下如果單核或者單處理器的處理能力已經(jīng)被應(yīng)用程序用盡,那么使用OpenMP使之成為多線程應(yīng)用程序肯定可以增進性能。
應(yīng)用程序需要跨平臺。OpenMP是一個廣受支持的跨平臺API庫,而且因為OpenMP通過編譯器指令實現(xiàn),故而使得使用了OpenMP的應(yīng)用程序能夠在不支持OpenMP標(biāo)準(zhǔn)的編譯器上編譯通過。
需要并行循環(huán)。OpenMP最多地被用以循環(huán)并行化,如果應(yīng)用程序有一些沒有循環(huán)依賴的循環(huán),使用OpenMP是個好主意。
最后的優(yōu)化需要。因為使用OpenMP不需要對已有的程序傷筋動骨,所以它是一個理想的進行小改動而獲取性能增進好工具。
?????? 如上所言,OpenMP不能用來處理所有多線程問題。從根源上說OpenMP就有所偏重,因為它原本是為高性能計算社區(qū)的應(yīng)用需要而開發(fā)的,所以它在有大量數(shù)據(jù)共享且含有復(fù)雜循環(huán)體的循環(huán)中表現(xiàn)更優(yōu)異。
?????? 就像使用原生線程會有額外開銷一樣,使用OpenMP也不是不用付出代價的。要想從OpenMP獲取性能提升就必須讓并行區(qū)域的加速比大于線程組的開銷。Visual C++的實現(xiàn)中是在第一次執(zhí)行到并行區(qū)域的時候生成線程組,然后把這些線程掛起直到再次利用。OpenMP內(nèi)部使用Windows線程池。圖9展示了OpenMP在雙處理器機器上執(zhí)行本文開始時的例子程序進行不同迭代次數(shù)的加速比,大約在1.7倍左右,這在雙處理器系統(tǒng)上是很典型的。(圖中Y軸的刻度顯示串行性能與并行性能的比率。)可以看到在迭代少于5000次之前并行版本更慢,而原因就在于并行帶來的線程開銷沖抵了并行的優(yōu)勢;大多數(shù)多次迭代的并行循環(huán)都要比串行版本理會快,但這與每一次迭代執(zhí)行的任務(wù)大小相關(guān),并非使用了OpenMP就會增進性能。
(圖9)雙處理器上串行與并行的性能比較
?????? OpenMP的編譯器指令雖然易于使用,但沒有提供強大的錯誤反饋功能。如果你正在編寫一個進度很趕的程序,需要能夠快速檢測到錯誤并且容易解決錯誤,那OpenMP可能不是適合于你的工具(最少當(dāng)前實現(xiàn)的OpenMP不是)。例如當(dāng)OpenMP不能為并行區(qū)域創(chuàng)建線程或者不能創(chuàng)建一個臨界區(qū)都會導(dǎo)致未定義的行為,Visual C++ 2005實現(xiàn)的OpenMP運行時例程會繼續(xù)嘗試,直到退出。我們計劃在接下來的OpenMP未來版本中增加標(biāo)準(zhǔn)錯誤報告機制。
?????? 另外需要注意的是在OpenMP線程之外使用Windows線程,因為OpenMP建立在Windows線程之上(譯者注:此處僅止VC2005上的OpenMP實現(xiàn)),所以它們處理同樣的過程是一樣。而問題在于OpenMP對于非自己創(chuàng)建的Windows線程一無所知,這導(dǎo)致兩個棘手的麻煩:OpenMP不會對Windows線程進行計數(shù)和OpenMP同步例程并不同步Windows線程——因為它們不是線程組的一部分。
?
OpenMP常見缺陷
?????? 雖然OpenMP可以輕易地為應(yīng)用程序增加并行能力,但仍然必須知道另一些事情:默認(rèn)情況下最外層并行for循環(huán)的索引變量是是私有的,但在嵌套的并行for循環(huán)里是共享的。當(dāng)存在循環(huán)嵌套的時候,你經(jīng)常希望內(nèi)部循環(huán)的索引變量是私有的,那就需要用private子句來指定這些變量。
?????? 編寫OpenMP應(yīng)用程序時應(yīng)該在拋出C++異常時加倍小心。特別地,當(dāng)應(yīng)用程序在并行區(qū)域拋出一個異常,這個異常必須被同一并行區(qū)域的同一線程處理,而不應(yīng)讓它外流。一個普遍法則就是:如果在并行區(qū)域可能拋出異常,那就必須捕捉它;如果沒有在拋出異常的并行區(qū)域捕捉到它,應(yīng)用程序通常情況下會崩潰。
?????? #pragma omp <directive> [clause]語句必須以換行符結(jié)束,而不是用以標(biāo)識代碼塊開始的大括號。以大括號結(jié)束的指令會導(dǎo)致編譯錯誤:
// Bad Bracket
#pragma omp parallel {
?? // won't compile Code
}
?
// Good Bracket
#pragma omp parallel
{
?? // Code
}
使用Visual C++ 2005調(diào)試OpenMP應(yīng)用程序有時候會比較麻煩。特別是在使用F10/F11鍵進入或者跳出并行區(qū)域的時候,簡直跟星際旅行差不多!這是因為編譯器增加很多額外的代碼去調(diào)用運行庫和調(diào)用線程組,而調(diào)試器卻并不知道這些,所以程序員看起來調(diào)試器行為跟以前一樣。我們建議在并行區(qū)域內(nèi)設(shè)置一個斷點,然后用F5運行到斷點處;跳出并行區(qū)域可以在并行區(qū)域外設(shè)置一個斷點,然后再按F5。
?????? 在并行區(qū)域執(zhí)行時,調(diào)試器的“Threads Windows”里將顯示當(dāng)前線程組的多個線程,但這里的線程ID與OpenMP線程ID是無關(guān)的,它只是Windows線程ID,因為OpenMP構(gòu)建在Windows線程之上。
?????? OpenMP現(xiàn)在不支持剖分導(dǎo)向優(yōu)化(Profile Guided Optimization,PGO),幸運的是OpenMP是基于編譯器指令的,你可以分別在/openmp和PGO兩種配置下編譯程序,以確定哪一種方法更加改進性能。
?
OpenMP與.Net
?????? 高性能計算與.Net乍聽起來似乎風(fēng)牛馬不相及,但Visual C++ 2005在這方面有了很大進步。我們做的其中一件事就是讓OpenMP可以在托管C++代碼里工作,只要讓/openmp開關(guān)與/clr和/clr:OldSyntax同時使用即可。這意味著你能夠在接受垃圾收集的.net類型的方法里并行執(zhí)行代碼。但請記住現(xiàn)在OpenMP與/clr:saft和/clr:pure并不兼容,我們計劃在以后實現(xiàn)它們的兼容。
?????? 同時使用OpenMP和托管代碼的另一件要事是使用了OpenMP的應(yīng)用程序只能運行在單獨的進程空間;如果其它的應(yīng)用程序裝載一個已經(jīng)裝載OpenMP的進程到自己的空間,那這個應(yīng)用程序可能會異常中止。
?????? OpenMP是應(yīng)用程序增加并行能力的簡單有效的工具,它提供并行化數(shù)據(jù)處理循環(huán)和功能化代碼塊的多種途徑。它易于集成到已有的應(yīng)用程序且可以簡單地通過編譯器開關(guān)來使用或者停用。OpenMP是一個充分利用多核CPU強大處理能力的簡單方法。最后我們強烈建議你閱讀OpenMP文檔以了解更多細(xì)節(jié),祝多線程之旅愉快!
?
Kang Su Gatlin Visual C++開發(fā)團隊的程序經(jīng)理,日常工作是找出讓程序運行更快的系統(tǒng)方案。加入到微軟之前他從事高性能和網(wǎng)格計算相關(guān)的工作。
?
Pete Isensee 微軟Xbox高級技術(shù)組的開發(fā)經(jīng)理,他在游戲行業(yè)有12年經(jīng)驗,并經(jīng)常關(guān)于優(yōu)化和性能方面的會議上發(fā)表演講。
?
賴勇浩 網(wǎng)易廣州的游戲程序員,對并行/分布式運算有強烈興趣,熟悉GameAI、算法優(yōu)化、代碼優(yōu)化等。
?
?
總結(jié)
以上是生活随笔為你收集整理的OpenMP与C++ 事半功倍地获得多线程的好处 下的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android PreferenceSc
- 下一篇: avx指令+openmp多线程实现一个基