Preface:在此我們已經都知道可以使用
#pragma parallel for 來對 for 迴圈進行平行化, 接著我們來看一個 2 兩層 for 迴圈的平行化:
- w1.c
- #include
- #include
- #include
-
- int gc=0;
-
- void Test(int n, int m)
- {
- printf(" - %d, %d\n", omp_get_thread_num(), n, m);
- gc++;
- }
-
- int main()
- {
- int i=0, j=0;
- #pragma omp parallel for
- for(i=0; i<8; i++)
- {
- for(j=0; j<3; j++) Test(i, j);
- }
- printf("gc=%d\n", gc);
- }
直覺來猜變數 gc 的值應該是兩層 for 迴圈跑的次數相乘, 所以經該是
3x8=24. 但輸出結果卻不如預期. 一個範例的執行結果如下:
- 0, 0
- 0, 1
- 0, 2
...(中間省略)...
- 6, 1
- 6, 2
gc=18
Clause "private" Usage:如果大家有修過
計算機結構, 應該知道一行指令如
a+=1, 當細化到組合語言時, 會有如下步驟:
Register < Var_a
Register < Register + 1
Var_a < Register
在只有一個 CPU 執行環境下執行當行時, 每行程式碼都是一行行按照順序執行 (
因為程式碼是 sequence 執行, 所以對應的組碼都是以程式碼為單位執行); 但如果考慮到有多顆 CPU 且是以平行運算來執行程式碼時, 則同一行程式碼有可能同時被兩個 CPU 同時執行, 此時兩個 CPU 在執行組碼如果錯開執行, 就會發生恐怖的 Race condition. 首先來一個幸運的 case, 就是當兩個 CPU 執行同一個程式碼時, 其對應的組碼並沒有發生 Overlapping:

因為 a=8 時 a+=1 執行兩次應該是 10, 所以上面的結果是對的, 但是當組碼有 Overlapping 且又運氣不好時, 下面的結果就會造成非預期結果:

上面問題就發生在第二行, 當 CPU1 執行
Register = Register + 1 時, CPU2 緊接著執行
Register = Var_a, 導致 CPU1 的運算結果本來應該是 9, 卻被改成 8! 而事實上 Race condition 的狀況又不是 always 可以 reproduce 的, 因此要 debugging 此類的 bug 通常也是很費力... Orz.
回到我們上面的程式碼, 因為變數
i, j 定義在
#pragma parallel for 的外面, 因此當平行程式在執行時, 變數
i, j 會類似於 global variables. 而問題不是發生在第一層 loop, 而是第二層的 for loop 中的變數
j! 因為變數
j 同時被多個線程 (
或 CPU) 讀寫, 導致上面解釋的狀況發生因而執行的整體 loop 次數不如預期. 要解這個問題有兩個發法.
一, 將有 Race condition 疑慮的變數定義到 #pragma parallel for 裡簡單說就是將 global variable 變成 local variable, 這樣每個線程都有自己的變數
j, 如此便不會發生彼此改寫結果的問題:
- w1_f1.c
- #include
- #include
- #include
-
- int gc=0;
-
- void Test(int n, int m)
- {
- printf(" - %d, %d\n", omp_get_thread_num(), n, m);
- gc++;
- }
-
- int main()
- {
- int i=0;
- #pragma omp parallel for
- for(i=0; i<8; i++)
- {
- int j;
- for(j=0; j<3; j++) Test(i, j);
- }
- printf("gc=%d\n", gc);
- }
二, 使用 OpenMP 的 private clause第二個方法是使用 OpenMP 的 clause
private 來告訴 OpenMP 那些變數應該是每個線程都應該有自己的一個位置儲存:
- w1_f2.c
- #include
- #include
- #include
-
- int gc=0;
-
- void Test(int n, int m)
- {
- printf(" - %d, %d\n", omp_get_thread_num(), n, m);
- gc++;
- }
-
- int main()
- {
- int i=0, j=0;
- #pragma omp parallel for private(j)
- for(i=0; i<8; i++)
- {
- for(j=0; j<3; j++) Test(i, j);
- }
- printf("gc=%d\n", gc);
- }
一個使用 gcc 編譯的 shell script 範例如下:
- w1.sh
- #!/bin/sh
- echo "Clearning..."
- test -f w1.out && rm -f w1.out
- test -f w1_f1.out && rm -f w1_f1.out
- test -f w1_f2.out && rm -f w1_f2.out
-
- echo "Compiling..."
- #gcc -std=c99 a.c -o a.out
- gcc -std=c99 -O1 -fopenmp w1.c -o w1.out
- gcc -std=c99 -O1 -fopenmp w1_f1.c -o w1_f1.out
- gcc -std=c99 -O1 -fopenmp w1_f2.c -o w1_f2.out
- echo "Running w1:"
- ./w1.out
- echo ""
- echo "Running w1 fix1:"
- ./w1_f1.out
- echo ""
- echo "Running w2 fix2:"
- ./w1_f2.out
This message was edited 19 times. Last update was at 08/03/2014 12:51:42