本节提供了一些示例来说明自动确定作用域规则的工作原理。这些规则在parallel 构造的作用域规则和任务构造中标量变量的作用域规则中进行了介绍。
示例 6-4 说明自动确定作用域规则的复杂示例1. REAL FUNCTION FOO (N, X, Y) 2. INTEGER N, I 3. REAL X(*), Y(*) 4. REAL W, MM, M 5. 6. W = 0.0 7. 8. C$OMP PARALLEL DEFAULT(__AUTO) 9. 10. C$OMP SINGLE 11. M = 0.0 12. C$OMP END SINGLE 13. 14. MM = 0.0 15. 16. C$OMP DO 17. DO I = 1, N 18. T = X(I) 19. Y(I) = T 20. IF (MM .GT. T) THEN 21. W = W + T 22. MM = T 23. END IF 24. END DO 25. C$OMP END DO 26. 27. C$OMP CRITICAL 28. IF ( MM .GT. M ) THEN 29. M = MM 30. END IF 31. C$OMP END CRITICAL 32. 33. C$OMP END PARALLEL 34. 35. FOO = W - M 36. 37. RETURN 38. END
在此示例中,函数 FOO() 包含一个 parallel 构造,其中包含一个 single 构造、一个工作共享 do 构造以及一个 critical 构造。
在 parallel 构造中使用了变量 I、N、MM、T、W、M、X 和 Y。编译器按照如下方式确定这些变量的作用域:
标量 I 是工作共享 do 循环的循环索引。OpenMP 规范要求将 I 的作用域确定为 private。
标量 N 在并行构造中是只读的,因此不会导致数据争用,这样,按照规则 PS1,将其作用域确定为 shared。
任何执行并行构造的线程都会执行第 14 行,将标量 MM 的值设置为 0.0。这一写入操作会导致数据争用,因此规则 PS1 不适用。由于在同一线程中该写入操作发生在读取 MM 之前,因此,按照规则 PS2 将 MM 的作用域确定为 private。
同样,将标量 T 的作用域确定为 private。
先读取标量 W,然后在第 21 行写入该标量,因此,规则 PS1 和 PS2 不适用。加法运算同时符合结合律和交换律,因此,按照规则 PS3,将 W 的作用域确定为 reduction(+)。
在第 11 行写入标量 M,该行位于 single 构造内。single 构造末尾的隐式屏障可确保第 11 行的写入不会与第 28 行的读取或第 29 行的写入同时发生,并且后两者不会同时发生,因为它们都位于同一 critical 构造内。没有任何两个线程可以同时访问 M。因此,在 parallel 构造中写入和读取 M 都不会导致数据争用,按照规则 S1,将 M 的作用域确定为 shared。
数组 X 在构造中是只读的,不进行写入,因此,按照规则 PA1,将其作用域确定为 shared。
写入数组 Y 的操作分布在各个线程中,没有任何两个线程会对 Y 的相同元素进行写入。由于不会发生数据争用,因此,按照规则 PA1 将 Y 的作用域确定为 shared。
static void par_quick_sort (int p, int r, float *data) { if (p < r) { int q = partition (p, r, data); #pragma omp task default(__auto) if ((r-p)>=low_limit) par_quick_sort (p, q-1, data); #pragma omp task default(__auto) if ((r-p)>=low_limit) par_quick_sort (q+1, r, data); } } int main () { ... #pragma omp parallel { #pragma omp single nowait par_quick_sort (0, N-1, &Data[0]); } ... } er_src shows the following compiler commentary: Source OpenMP region below has tag R1 Variables autoscoped as FIRSTPRIVATE in R1: p, q, data Firstprivate variables in R1: data, p, q 47. #pragma omp task default(__auto) if ((r-p)>=low_limit) 48. par_quick_sort (p, q-1, data); Source OpenMP region below has tag R2 Variables autoscoped as FIRSTPRIVATE in R2: q, r, data Firstprivate variables in R2: data, q, r 49. #pragma omp task default(__auto) if ((r-p)>=low_limit) 50. par_quick_sort (q+1, r, data);
标量变量 p 和 q 以及指针变量数据在 task 构造和 parallel 构造中都是只读的。因此,根据 TS1 自动将其作用域确定为 firstprivate。
示例 6-6 斐波纳契示例int fib (int n) { int x, y; if (n < 2) return n; #pragma omp task default(__auto) x = fib(n - 1); #pragma omp task default(__auto) y = fib(n - 2); #pragma omp taskwait return x + y; } er_src shows the following compiler commentary: Source OpenMP region below has tag R1 Variables autoscoped as SHARED in R1: x Variables autoscoped as FIRSTPRIVATE in R1: n Shared variables in R1: x Firstprivate variables in R1: n 24. #pragma omp task default(__auto) /* shared(x) firstprivate(n) */ 25. x = fib(n - 1); Source OpenMP region below has tag R2 Variables autoscoped as SHARED in R2: y Variables autoscoped as FIRSTPRIVATE in R2: n Shared variables in R2: y Firstprivate variables in R2: n 26. #pragma omp task default(__auto) /* shared(y) firstprivate(n) */ 27. y = fib(n - 2); 28. 29. #pragma omp taskwait 30. return x + y; 31. }
标量 n 在 task 构造和 parallel 构造中都是只读的。因此,根据 TS1 自动将 n 的作用域确定为 firstprivate。
标量变量 x 和 y 是函数 fib() 的局部变量。在两个任务中访问 x 和 y 不会导致数据争用。由于存在一个 taskwait,因此这两个任务将在执行 fib() 的线程(遇到并生成了这两个任务)退出 fib() 之前完成执行。这意味着 x 和 y 将在这两个任务执行时处于可访问状态。因此,根据 TS2 自动将 x 和 y 的作用域确定为 shared。
示例 6-7 使用 single 和 task 构造的示例int main(void) { int yy = 0; #pragma omp parallel default(__auto) shared(yy) { int xx = 0; #pragma omp single { #pragma omp task default(__auto) // task1 { xx = 20; } } #pragma omp task default(__auto) // task2 { yy = xx; } } return 0; } er_src shows the following compiler commentary: Source OpenMP region below has tag R1 Variables autoscoped as PRIVATE in R1: xx Private variables in R1: xx Shared variables in R1: yy 7. #pragma omp parallel default(__auto) shared(yy) 8. { 9. int xx = 0; 10. Source OpenMP region below has tag R2 11. #pragma omp single 12. { Source OpenMP region below has tag R3 Variables autoscoped as SHARED in R3: xx Shared variables in R3: xx 13. #pragma omp task default(__auto) // task1 14. { 15. xx = 20; 16. } 17. } 18. Source OpenMP region below has tag R4 Variables autoscoped as PRIVATE in R4: yy Variables autoscoped as FIRSTPRIVATE in R4: xx Private variables in R4: yy Firstprivate variables in R4: xx 19. #pragma omp task default(__auto) // task2 20. { 21. yy = xx; 22. } 23. }
在此示例中,xx 是 parallel 构造中的专用变量。组中的其中一个线程会修改 xx 的初始值(通过执行 task1)。然后,所有线程都会遇到使用 xx 执行某些计算的 task2。
在 task1 中,使用 xx 不会导致数据争用。由于 single 构造结尾有一个隐式屏障,而且 task1 应在退出此屏障之前完成,因此在 task1 正在执行时可以访问 xx。因此,根据 TS2,在 task1 中自动将 xx 的作用域确定为 shared。
在 task2 中,xx 的使用是只读的。但是,xx 的使用在包含它的 parallel 构造中不是只读的。由于 xx 在 parallel 构造中预先确定为 private,因此无法确定在 task2 正在执行时是否可以访问 xx。因此,根据 TS3,在 task2 中自动将 xx 的作用域确定为 firstprivate。
在 task2 中,使用 yy 会导致数据争用,在每个执行 task2 的线程中,在读取变量 yy 之前始终先由同一线程写入该变量。因此,根据 TS4,在 task2 中自动将 yy 的作用域确定为 private。
示例 6-8 使用 task 和 taskwait 构造的示例int foo(void) { int xx = 1, yy = 0; #pragma omp parallel shared(xx,yy) { #pragma omp task default(__auto) { xx += 1; #pragma omp atomic yy += xx; } #pragma omp taskwait } return 0; } er_src shows the following compiler commentary: Source OpenMP region below has tag R1 Shared variables in R1: yy, xx 5. #pragma omp parallel shared(xx,yy) 6. { Source OpenMP region below has tag R2 Variables autoscoped as SHARED in R2: yy Variables autoscoped as FIRSTPRIVATE in R2: xx Shared variables in R2: yy Firstprivate variables in R2: xx 7. #pragma omp task default(__auto) 8. { 9. xx += 1; 10. 11. #pragma omp atomic 12. yy += xx; 13. } 14. 15. #pragma omp taskwait 16. }
在 task 构造中,xx 的使用不是只读的,且会导致数据争用。但是,在任务中读取 x 将获取在任务外定义的 x 值(因为 xx 在 parallel 构造中为 shared)。因此,根据 TS5,自动将 xx 的作用域确定为 firstprivate。
在 task 构造中,yy 的使用不是只读的,但不会导致数据争用。由于存在 taskwait,因此可在执行任务时访问 yy。因此,根据 TS2,自动将 yy 的作用域确定为 shared。