下面的示例说明了自动作用域规则的工作原理。
示例 6-3 更复杂的示例
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() 包含一个并行区域,该并行区域包含一个 SINGLE 构造、一个工作共享 DO 构造和一个 CRITICAL 构造。如果我们忽略所有 OpenMP 并行构造,则并行区域中的代码所执行的操作如下:
将数组 X 中的值复制到数组 Y
查找 X 中的最大正数值,并将其存储在 M 中
将 X 的一些元素的值累加到变量 W 中。
让我们看一下编译器如何使用上述规则来查找并行区域中变量的相应作用域。
在并行区域中使用下列变量:I、N、MM、T、W、M、X 和 Y。编译器将确定以下内容。
标量 I 是工作共享 DO 循环的循环索引。OpenMP 规范要求将 I 的作用域确定为 PRIVATE。
标量 N 在并行区域中只被进行读取,因此不会导致数据争用,这样,按照规则 S1,将其作用域确定为 SHARED。
任何执行并行区域的线程都会执行语句 14,以便将标量 MM 的值设置为 0.0。这一写入操作会导致数据争用,因此规则 S1 不适用。由于是在同一线程中读取 MM 之前出现该写入操作,因此,按照规则 S2,将 MM 的作用域确定为 PRIVATE。
同样,将标量 T 的作用域确定为 PRIVATE。
先读取标量 W,然后将其写入语句 21,因此,规则 S1 和 S2 不适用。加法运算同时符合结合律和交换律,因此,按照规则 S3,将 W 的作用域确定为 REDUCTION(+)。
标量 M 写入语句 11,该语句位于 SINGLE 构造内。SINGLE 构造末尾的隐式屏障可确保写入语句 11 不会与读取语句 28 或写入语句 29 同时发生,并且后两者不会同时发生,因为它们都位于同一 CRITICAL 构造内。没有任何两个线程可以同时访问 M。因此,在并行区域中写入和读取 M 都不会导致数据争用,按照规则 S1,将 M 的作用域确定为 SHARED。
数组 X 在区域中只被进行读取,不进行写入,因此,按照规则 A1,将其作用域确定为 SHARED。
写入数组 Y 的操作分布在各个线程中,没有任何两个线程会写入 Y 的相同元素。由于不存在数据争用,因此,按照规则 A1,将 Y 的作用域确定为 SHARED。
示例 6-4 使用 QuickSort 的示例
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 result: 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 以及指针变量数据在任务构造和并行区域中都是只读的。因此,根据 TS1 自动将其作用域确定为 FIRSTPRIVATE。
示例 6-5 另一个示例
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 result: 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 在任务构造和并行构造中都是只读的。因此,根据 TS1 自动将 n 的作用域确定为 FIRSTPRIVATE。
标量变量 x 和 y 是函数 fib() 的局部变量。在两个任务中访问 x 和 y 不会导致数据争用。由于存在一个 taskwait,因此这两个任务将在执行 fib() 的线程(遇到了这两个任务)退出 fib() 之前完成执行;这意味着 x 和 y 将在这两个任务执行时出现。因此,根据 TS2 自动将 x 和 y 的作用域确定为 SHARED。
示例 6-6 另一个示例
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 result: 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 是并行区域中的专用变量。组中的其中一个线程会修改 xx 的初始值(通过执行 task1)。然后,所有线程都会遇到使用 xx 执行某些计算的 task2。
在 task1 中,使用 xx 不会导致数据争用。由于单个构造结尾有一个隐式屏障,而且 task1 应在退出此屏障之前完成,因此,xx 将在 task1 执行时出现。所以,根据 TS2,在 task1 中自动将 xx 的作用域确定为 SHARED。
在 task2 中,xx 的使用是只读的。但是,xx 的使用在包含它的并行构造中不是只读的。由于 xx 在并行构造中预先确定为 PRIVATE,因此无法确定 xx 将在执行 task2 时出现。所以,根据 TS3,在 task2 中自动将 xx 的作用域确定为 FIRSTPRIVATE。
在 task2 中,使用 yy 会导致数据争用,在每个执行 task2 的线程中,在读取变量 yy 之前始终先由同一线程写入该变量。所以,根据 TS4,在 task2 中自动将 yy 的作用域确定为 PRIVATE。
示例 6-7 另一个示例
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 result: 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. }
在任务构造中,xx 的使用不是只读的,且会导致数据争用。但在任务区域中读取 x 会获取在任务之外定义的 x 值。(在此示例中,由于 xx 对于并行区域确定为 SHARED,因此 x 的定义实际上在并行区域之外。)所以,根据 TS5,自动将 xx 的作用域确定为 FIRSTPRIVATE。
在任务构造中,yy 的使用不是只读的,但不会导致数据争用。由于存在 taskwait,因此可在执行任务时访问 yy。所以,根据 TS2,自动将 yy 的作用域确定为 SHARED。