最近寫水動力的程序,體系太大,必須用並行才能算的動,無奈只好找了並行編程的資料學習了。我想我沒有必要在博客里開一個什么並行編程的教程之類,因為網上到處都是,我就隨手記點重要的筆記吧。這里主要是openmp的~
1 臨界與歸約
在涉及到openmp的並行時,最需要注意的就是被並行的區域中的公共變量,對於需要reduce的變量,尤其要注意,比如這段代碼:
program main implicit none include 'omp_lib.h' integer N,M,i real(kind=8) t N=20000 t=0.0 !$OMP PARALLEL DO do i=1,N t=t+float(i); M=OMP_get_num_threads() enddo write(*, "('t = ', F20.5, ' running on ', I3, ' threads.')") t,M pause stop end
串行代碼可以很容易的得到正確結果:
t = 200010000.00000 running on 1 threads.
不幸的是,如果是並行的話,可能每次都得到一個不同的結果:
t = 54821260.00000 running on 8 threads.
t = 54430262.00000 running on 8 threads.
....
原因很簡單,假設do被並行了兩個線程,A1,A2,則每個線程都可以t,在其中一個線程訪問t的時候,另一個線程修改了t,導致t的某些值“丟了”。解決方法有兩種,第一種就是“臨界”,就是鎖定t:
!$OMP PARALLEL DO do i = 1, N !$OMP CRITICAL t = t+float(i) !$OMP END CRITICAL M = OMP_get_num_threads() enddo
這樣每個時刻只有一個線程能訪問這個變量。顯然,這種方法會遇到“短木板瓶頸”,更高效的方法是使用“歸約”:
!$OMP PARALLEL DO REDUCTION(+:t) do i = 1, N t = t+float(i) M = OMP_get_num_threads() enddo
此時程序會自動在內部實現儲存部分和之類的操作。這個方法比臨界要高效的多,這是我這里運行的結果:臨界0.005s, 歸約0.003s。對於大任務,速度會更快。
2 條件並行
有時,對於小的循環,多線程的消耗超過了並行的節省時間,顯然這是就不值得並行了。比如
do i = 1, N t = t+(sin(float(i))+2.0)**0.3+abs(cos(log(float(i))))**0.7 M = OMP_get_num_threads() enddo
發現:
N 20000 5000
tserial 0.027s 0.003
tparallel 0.013s 0.004
推斷在N>5000時應該並行更有效,可以加上條件編譯:
!$OMP PARALLEL DO REDUCTION(+:t) if(N > 5000)
3 負載平衡
不同線程間的工作量“不平等”是個很麻煩的問題,他會大大降低程序並行效率,比如這個程序:
N = 5000 !$OMP PARALLEL DO PRIVATE(j) do i = 1, N do j = i, N a(j, i) = fun(i, j) enddo enddo
其中fun是個費時的函數,串行與8核CPU並行的時間比較:
serial:3m28.007s;paralle:49.940s 加速比 4.1 太低了
這個顯然與CPU個數無關。分析上面的循環發現,i=1時內層需要N個循環,而i=2500時候內部僅僅N/2個循環,極其不平衡,因此可以顯式指定其調動模式,改進負載平衡。NAMD中有個LDB模塊就是干這個的。SCHEDULE一般格式:
SCHEDULE(type, chunk)
可以比較一下:
!$OMP PARALLEL DO SCHEDULE(static,1) 34.955s !$OMP PARALLEL DO SCHEDULE(dynamic,1) 29.773s !$OMP PARALLEL DO SCHEDULE(guided,1) 53.116s !$OMP PARALLEL DO SCHEDULE(static,500) 48.822s !$OMP PARALLEL DO SCHEDULE(dynamic,500) 50.485s !$OMP PARALLEL DO SCHEDULE(guided,500) 51.611s
需要注意的是,實際中很難一下看出那種調度方式最好。通常需要實際試驗,這還與你調用的CPU數目有關。SCHEDULE中,增大chunk可以提高緩存命中率,但是以降低負載平衡為代價的