簡易的程式平行化方法-OpenMP(一)簡介

本文原發表於:http://heresy.spaces.live.com/blog/cns!E0070FB8ECF9015F!1018.entry


嗯∼首先,Heresy 也是最近才開始試著用 openMP 的,所以其是這篇與其說是教學或介紹,倒不如說是學習心得會更為恰當。會不會繼續用?說實話也是未知數。總之,看著辦囉∼

也希望有人對這東西有研究的話,能多多指教。


多執行緒的概念

目前雙核心的 CPU 當道,AMD 的 Athlon64x2、Intel 的 Pentium-D、Core Duo,以及即將上市的 Core 2 Duo,儼然將成為下一代電腦的主流(尤其是超低價的 Pentium D,絕對是現階段 C/P 值極高的雙核心 CPU)。但是雙核心有什麼用呢?

對於一般單一執行緒(single thread)的程式,多核心的處理器並沒有辦法提升它的處理效能;不過對於多執行緒(multi thread)的程式,就可以透過不同的核心同時計算,來達到加速的目的了!簡單的例子,以單執行緒的程式來說,一件事做一次要十秒的話,要做十次,都丟給同一顆核心做的話,自然就是 10 秒 * 10 次,也就是 100 秒了;但是以多執行緒的程式來說,它可以把這一件事,分給兩顆核心各自做,每顆核心各做 5 次,所以所需要的時間就只需要 50 秒!

當然,多執行緒的程式實際上沒這麼簡單。在工作的切割、結合上,也是要多花時間的,所以在現實中,即使最佳狀況,雙核心的效能也不會是 1 1 = 2 這樣的理想化。除此之外,也不是所有工作都是可以切割的!很多工作是有關聯性的,這樣如果直接切割給不同的處理核心各自去平行運算,出來的結果是肯定有問題的。而且,多執行緒的程式在編寫、維護上,也都比單一執行緒的程式複雜上不少。

不過,如果電腦本身是多處理器、多核心處理器,或是處理器擁有像 Intel Hyper-Threading Technology 這類的能在同一個時間處理多個執行緒的功能的話,那把各自獨立的工作由單一執行緒改成多執行緒,在執行的效率上,大多還是會有增進的!


多執行緒的程式

寫程式的時候該怎麼去寫多執行緒的程式呢?一般的方法,就是真的利用 thread 的控制,去實際在程式中去產生其他的 thread 來處理。像 POSIX Threads 這套 library,就是用來產生、控制執行緒的函式庫。而像 Microsoft VisualStudio 2005 中,也有提供控制 thread 的功能。這種方法,大多就是產生多個 thread,而再由主要的 thread 把工作拆開,分給各 thread 去運算,最後再由主要的 thread 回收結果、整合。

但是,實際上要去控制 thread 是滿麻煩的∼在程式的編寫上,也會複雜不少;而如果我們只是想要把一些簡單的迴圈平行化處理,用 thread library 來控制,實在有點殺雞用牛刀的感覺。這時候,用 Open MP 就簡單多了!OpenMP 是一種能透過高階指令,很簡單地將程式平行化、多執行緒化的 API;在最簡單的情形,甚至可以只加一行指令,就可以將迴圈內的程式平行化處理了!


OpenMP 的基本使用

要在 Visual C 2005 中使用 openMP 其實不難,只要將 Project 的 Properties 中 C/C 裡 Language 的 OpenMP Support 開啟(參數為 /openmp),就可以讓 VC 2005 在編譯時支援 OpenMP 的語法了;而在使用到 OpenMP 的檔案,則需要先 include OpenMP 的 header file : omp.h。

而要將 for 迴圈平行化處理,該怎麼做呢?非常簡單,只要在前面加上一行

#pragma omp parallel for

就夠了!

也可以實際用一段簡單的程式,來弄清楚它的運作方式。

#include 
#include

void Test( int n )
{
for( int i = 0; i < 10000; i )
{
//do nothing, just waste time
}
printf( "%d, ", n );
}

int main(int argc, char* argv[])
{
for( int i = 0; i < 10; i )
Test( i );

system( "pause" );
}

上面的程式,在 main() 是一個很簡單的迴圈,跑十次,每次都會呼叫 Test() 這個函氏,並把是迴圈的執行次數(i)傳進 Test() 並列印出來。想當然耳,它的結果會是:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

而如果想利用 OpenMP 把 mian() 裡面的迴圈平行化處理呢?只需要修改成下面的樣子:

#include 
#include
#include

void Test( int n )
{
for( int i = 0; i < 10000; i )
{
//do nothing, just waste time
}
printf( "%d, ", n );
}

int main(int argc, char* argv[])
{
#pragma omp parallel for
for( int i = 0; i < 10; i )
Test( i );

system( "pause" );
}

夠簡單吧?重頭到尾,只加了兩行(紅色部分)!而執行後,可以發現結果也變了!

0, 5, 1, 6, 2, 7, 3, 8, 4, 9,

可以從結果很明顯的發現,他沒有照著 0 到 9 的順序跑了!而上面的順序怎麼來的?其實很簡單,OpenMP 只是把迴圈 0 – 9 共十個步驟,拆成 0 – 4, 5 – 9 兩部份,丟給不同的執行緒去跑,所以數字才會出現這樣交錯性的輸出∼

而要怎麼確定真的有跑多執行緒呢?如果本來有多處理器、多核心處理器或有 Hyper Thread 的話,一個單執行緒程式,最多只會把一顆核心的使用量吃完;像比如說在 Pentium 4 HT 上跑,單一執行緒的程式,在工作管理員中看到的 CPU 使用率最多就是 50%。而利用 OpenMP 把回圈進行平行化處理後,就可以在執行廻圈時,把兩顆核心的 CPU 都榨光了!也就是工作管理員可以看到 CPU 使用率是 100%。


當然,OpenMP 不是只有這個指令的∼而且 OpenMP 也不適用於任何場合。請期待第二部份。 :p

不過理論上,應該會等測試過效率後,來列一些效能測試的結果。同時,會加一些錯誤利用 OpenMP 的示範。


註:

  1. Microsoft VisualStudio 2005 Express 版中,並沒有附上 OpenMP 這個 library,似乎是要 Standard 以上版本才有。
  2. OpenMP 官方網站:http://www.openmp.org
  3. OpenMP in Visual C (Microsoft MSDN Library):http://msdn2.microsoft.com/en-us/library/tt15eb9t.aspx

目錄:

43 thoughts on “簡易的程式平行化方法-OpenMP(一)簡介”

  1. 你好 請問一下 OpenMP 只有VC2005才有嗎我的是VC6.0 還有 要如何開啟使用它呢? 您寫的OpenMP 的基本使用 裡面的開啟方法 我不太懂不好意思麻煩你解答一下 謝謝

  2. 您好,Visual C 6.0 應該是不支援 OpenMP 的。如果想要用 VC6.0 的介面開發支援 OpenMP 程式,可能要搭配像 Intel C compiler 這種,其他有支援 OpenMP 的編譯器。http://www.intel.com/cd/software/products/asmo-na/eng/compilers/cwin/279578.htm

  3. 想請問你 OpenMP 變數為陣列的話共用上有線定陣列大小嗎因為我在測試上有出現錯誤它顯示OVERFOLW謝謝

  4. to dd抱歉,不太能確定你的問題。不知道能不能請你把有問題的程式的部分大至提供一下以供判斷?(有沒有可能是記憶體使用過多?)

  5. PROGRAM VEC_ADD_DO INTEGER N, CHUNKSIZE, CHUNK, I, PARAMETER (N=10000) PARAMETER (CHUNKSIZE=100) REAL A(N), B(N), C(N)! Some initializations DO I = 1, N A(I) = I * 1.0 B(I) = A(I) ENDDO CHUNK = CHUNKSIZE !$OMP PARALLEL SHARED(A,B,C,CHUNK) PRIVATE(I)!$OMP DO SCHEDULE(DYNAMIC,CHUNK) DO I = 1, N C(I) = A(I) B(I) ENDDO!$OMP END DO NOWAIT!$OMP END PARALLEL END如上程式 若N=100000則程式執行時會出現 stack overflow

  6. 不好意思,因為沒寫過fortran的程式,所以不知想法對不對.建議你可以先把fortran程式裡的openmp功能先關掉,看是否能正常執行.因為根據C程式的概念來推敲這個問題,stack overflow是因為在function內直接宣告過大的array所造成,與openmp無關.以C/C 的概念來說,若需要使用較大的記憶體空間,請使用global variable ,或是用pointer 來allocate.

  7. Openmp功能關掉可以正常執行開啟後才會出現錯誤謝謝你的回答順便請問你OPENMP可以將EXE檔LOAD進入指定的核心內嗎

  8. 抱歉,由於沒有寫過 fortran,所以只能把你的程式轉寫成 C 來執行;而同樣的程式在 C 裡執行似乎是沒有這樣問題的。此外,OpenMP 算是相當簡化的多執行緒寫法,所以應該是沒辦法透過 OpenMP 來指定運行的核心。

  9. OpenMP 是用來執行 shared memory parallelism的 directives標準。原本各家廠商都有自己的 shared memory directives的語法,但後來未求統一才有OpenMP的標準,目的只是希望程式的porting會比就容易點。OpenMP通常是將程式依需要,進行 task parallelism 或是 data parallelism。就以上列的程式而言,當一個變數(array)被宣稱為shared時,所有的 threads 共用該記憶體位置 logically。 (Note:之所以講 logically是因為各家實做不同,有的真的就 refer 到同一個memory address,有的則是 copy 一份到自己 local 但不時的加以 sync. ),當宣稱為private時就會在 local 端copy 一份,與其他thread互不關聯。以上是大致的情況。回到問題點,能否透過 OpenMP 將EXE檔LOAD進入指定的核心內,應該是不行的。如果要做這樣的動作,建議用 PVM,它可以 “spawn” user指定的執行檔到指定的processor上執行,不過PVM是distributed memory parallelism 的library, 跟OpenMP 不搭嘎。Overflow 的問題比較奇怪,照理講跟OpenMP沒有關係,可是為何會關掉後就沒錯誤。這點比較奇怪。 stack overflow通常就是heresy說的那樣。除非是您用的這版 OpenMP 為了方便,對stack size做了限定或是各 thread duplicates了一份。可否麻煩告知您使用的threads數目是多少?如果降低threads數,問題會不會仍然存在?如果您要直接解法的話,那可以將dimension的宣告方式(“real”, in your case)改成用common block。這樣就不會有stack 的限制了(當然,還是受 memory實體大小的限制啦)。By the way, 就算您的dimension沒有 reach到stack 的上限,但在 runtime 也可能會出問題。以上個人觀點,請多包涵。倒是對您將thread 數目減少後,何時會再發生stack overflow的結果感到蠻有趣的,如果有結果的話,麻煩分享一下。謝謝!

  10. 並且我在VC使用相同的程式執行後會出現錯誤這點讓我很納悶並不像heresy所說的可能是我OpenMP2.5版本的關係吧我在測試看看有結果在告知

  11. 您好,這是我在 VC 測試的程式#include #include #define N 10000#define CHUNKSIZE 100int main(){ int CHUNK, I; float A[N], B[N], C[N]; for( I = 0; I < N; I ) { A[I] = I * 1.0; B[I] = A[I]; } CHUNK = CHUNKSIZE; #pragma omp parallel shared( A, B, C, CHUNK ) #pragma omp for nowait schedule( dynamic, CHUNK ) for( I = 0; I < N; I ) { C[I] = A[I] B[I]; } return 0;}至少這樣的程式,Heresy 自己用 VisualStudio 2005 Professional 是沒問題的。

  12. 很抱歉你誤會我的意思了當N=100000 才會出現錯誤你的N=10000這個不會出現錯誤所以說你誤會了感激你的幫忙

  13. 您好如果是在 C 的話,超過 100,000 似乎的確也會不能執行;不過這點就是之前所提到,變數宣告方法的問題了。而在這個情況下,實際上和 OpenMP 是沒關係的∼即使把 OpenMP 關閉了,也還是會有同樣的錯誤。以這個例子來說,如果把 A, B, C 的宣告,改變為用 pointer 的方法就不會有問題了。也就是將float A[N], B[N], C[N];寫成float *A = new float[N], *B = new float[N], *C = new float[N];

  14. 很感謝你的回答與幫忙在FORTRAN內宣告陣列超過100,000 是可行的但加了OPENMP的功能卻不可行所以我才會詢問是否OPENMP有這方面的限制在此在說聲謝謝很高興有人可以幫忙與分享畢竟C 我不太清楚還在學習的階段所以不太清楚C的限制我會盡量摸索的以後有問題大家一起分享討論

  15. Heresy 在看 OpenMP 的文件時,似乎沒有發現相關的限制。或許可以考慮試試看在 FORTRAN 中也使用類似 C new 的這種 dynamic memory allocation 的方法試試看?

  16. 在別的地方看到的,Fortran Compiler需要加大Shell的Stack size上限,否則指定多個執行緒後會出現記憶體錯誤的訊息,使用方式是先執行ulimit -s 100000,-s代表修改Stack size,後面接的數字代表加到多大的上限。所以修改看看吧.

  17. 參考:http://www.ancad.com/blog/AnCADSupport/index.php/archives/4

  18. 引用 bylee:在別的地方看到的,Fortran Compiler需要加大Shell的Stack size上限,否則指定多個執行緒後會出現記憶體錯誤的訊息,使用方式是先執行ulimit -s 100000,-s代表修改Stack size,後面接的數字代表加到多大的上限。========================================其實VC 裡也是有同樣的Thread的stack size限制﹝應該所有Compiler都會遇到這樣的問題﹞,所以都得想辦法更改這個設定,才能正常執行。不過問題是,一般程式會用到如此大量的Thread似乎是不可能的,因為並不是Thread增多就一定會增加Performance,而是有個最佳的值﹝依據Processor、Core及OS的不同而有所不同﹞。

  19. 您好,我想請教一下關於變數平行化的問題一般來說當#pragma底下的可能變數會遇到多執行緒修改相同位置的值,所以我門會去設private那如果今天該變數為陣列型態呢?我一樣設private他會出現the variable is being used without defined的執行錯誤訊息,感覺是我一開始動態配置的空間不夠@”@不知道要怎麼解決或是有人有遇到過一樣的問題嗎?

  20. 請問您在這部分實際的程式是怎麼寫的?OpenMP 的 private 有限制不能是 incomplete type 或 reference type,不知道您是不是遇到這樣的問題?

  21. 我原本是先用malloc配置好記憶體空間然後使用#pragma omp parallel for去對迴圈平行化,但是這樣會有執行時的錯誤,後來我是將配置記憶體的地方搬進#pragma omp parallel for底下,就沒有問題了= =在想是不是因為原先配置好的一塊空間是給一個thread使用,但是今天有多個thread下去,空間大小就不敷使用了?這是我的猜想拉XD謝謝您的回覆

  22. to maxsize 您好,Heresy 是認為 OpenMP 沒辦法把用 malloc 宣告的陣列拿來處理;因為他並不知道他的實際大小,所以應該是屬於 incomplete type。

  23. 关于stack overflow ,只要修改VS.net project 属性,即可正常执行。 Scalar Local Variables AUTOMATIC

  24. 不好意思我想請問一下為什麼使用parallel for以後thread0會分到0-4而thread1則是5-9這是有什麼根據嗎為什麼不是thread0分到0,2,4,6,8然後thread1是1,3,5,7,9???麻煩幫小弟解惑好嗎

  25. 對於這類的資料平行化的架構,一般來說工作分割的順序大多應該都是沒有關係的,也就是不同的分割方法,不應該要影響到結果。而 OpenMP 預設會採取這樣的分割方法,應該也僅只是實作上的一種方法(某種程度來說,這算是最好分割的)。而實際上要控制他的分割方法的話,也還可以透過「schedule」來做些某種程度上的控制,建議可以參考:http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=16

  26. 您好我是國防醫學院傅老師實驗室的研究生,我在新灌的電腦裡想要執行新的miil程式,卻只能抓到2.0.200090710 BETA的版本,因為需要鎖定圖像的比例,能否請你們教我如何讓城市在更新為更新的版本(可以算比例跟orthographic版本??) 謝謝

  27. 您好,由於你們所需要的還是開發中功能,所以還沒有放到網路上公開下載。不知道可否麻煩你發封信給我,再把檔案寄給你?謝謝

  28. 你好 我的帳號是completedentures@hotmail.com非常謝謝你們

  29. 您好請教個關於OpenMP的問題若原程式已是多執行緒程式例如Thread1~threadN 做的事情相同 但針對不同矩陣運算for(int i=0;i

  30. 您好請教個關於OpenMP的問題若原程式已是多執行緒程式例如:Thread1~threadN 做的事情相同,但針對不同矩陣運算for(int i=0;i

  31. 您好,請教個關於OpenMP的問題,若原程式已是多執行緒程式.例如Thread1~threadN 做的事情相同.但針對不同矩陣運算.for(int i=0;i

  32. 您好請教個關於OpenMP的問題若原程式已是多執行緒程式例如Thread1~threadN 做的事情相同 但針對不同矩陣運算for(int i=0;i N;i ) sum = A[i];若將個各Thread for迴圈做OpenMP平行化處理Thread1~threadN 總運算時間會在加快運算速度嗎

  33. 您好請教個關於OpenMP的問題若原程式已是多執行緒程式例如Thread1~threadN 做的事情相同 但針對不同矩陣運算for(int i=0;i

  34. to bar您好,如果本來程式已經有透過 threading 來做平行化,且已經有把 CPU 的計算資源壓榨光的話(例如 CPU 使用率已經到 100%),那再去透過 OpenMP 來做平行化的增益應該不會太大,甚至有可能會因為額外的 overhade 而變慢。

  35. heresy您好,我想請問對於#pragma omp parallel for來說,他可以使用一般的靜態分配或是動態分配,那對於 #pragma omp parallel這種平行方式來說,他似乎沒有上述那兩種方式可以使用?經我測試後,似乎#pragma omp parallel這種建構平行區塊的方法本身就是動態的?想請教heresy是否有做過類似的測試呢?謝謝您

  36. to parallelisgood 您好。OpenMP 的 schedule 應該是僅針對 for 設定的;對於 parallel 的部分,似乎是沒有對應的指令。

  37. 所以對於#pragma omp parallel來說,他並不像對於for那樣分割好後平均給與各CPU,而是有可能是以動態的方式實做的?

  38. to parallelisgood 這部分比較不確定,可能要去查看看 OpenMP 的標準規範才能確認這個行為是否有定義。不過,不知道您是在怎樣的情況下,會需要去控制他的排程方式?

  39. to heresy:因為我平行的演算法是負載不平衡的演算法,所以我是希望能做到動態平行,而非一開始就分配好工作量的靜態平行,那我去查查OpenMP的規範書在來這邊回覆好了:D

  40. Heresy 這邊比較沒有用到這樣的狀況,所以比較不清楚了。也希望您確定後可以再分享一下結果了∼

  41. 您好

    近日因為研究論文需求,需要將既有之程式藉由openMP的方式來平行化,以提升其速度,

    以前都是使用Compaq Fortran來編譯程式,為了要使用openMP,

    改用了Intel Visual Fortran,在Microsoft Visual Studio 2010 Professional中,

    在還沒加入omp語法前,程式都可正常運行,

    因為是剛開始嘗試,我想先指定兩個執行序,也就是omp_set_num_threads(2),

    然而,卻會出現”stack overflow”的錯誤訊息,

    查詢了許多資訊後,嘗試增加”stack size”,然而,除了”stack overflow”的訊息依舊出現外,

    更多了一些”未載入kernel32.dll(或是ntdll.dll)的符號”等訊息,實在不曉得何處有問題,

    從您的部落格得知您對於程式編寫有相當豐富之經驗,不曉得您是否可以提供一些建議。

    謝謝您

  42. to Sammy
    抱歉,個人沒有在使用 Fortran,所以可能幫不上忙。

發佈回覆給「bylee」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *