このチュートリアルでは、データの競合を含んだ 2 つのプログラムを使用します。
最初のプログラムは素数を見つけます。このプログラムは C 言語で作成され、OpenMP 指令で並列化されています。ソースファイルは prime_omp.c と呼ばれます。
2 番目のプログラムも素数を見つけるもので、やはり C 言語で作成されます。ただし、これは OpenMP 指令でなく POSIX スレッドで並列化されます。ソースファイルは prime_pthr.c と呼ばれます。
このチュートリアルで使用するソースファイルは、Oracle Developer Studio 開発者ポータルのダウンロードエリアからダウンロードできます。
サンプルファイルをダウンロードして展開したあと、OracleDeveloperStudio12.5-Samples/ThreadAnalyzer ディレクトリからサンプルを見つけることができます。サンプルは、prime_omp および prime_pthr サブディレクトリにあります。各サンプルディレクトリには、手順に関する DEMO ファイルと Makefile ファイルが 1 つずつ含まれていますが、このチュートリアルではそれらの手順に従わず、Makefile も使用しません。代わりに、コマンドを個別に実行していきます。
このチュートリアルに従うには、サンプルディレクトリから prime_omp.c と prime_pthr.c ファイルを別のディレクトリにコピーするか、独自のファイルを作成し、次のコードリストからコードをコピーします。
このセクションでは、prime_omp.c のソースコードを次に示します。
1 /* 2 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All Rights Reserved. 3 */ 4 5 #include <stdio.h> 6 #include <math.h> 7 #include <omp.h> 8 9 #define THREADS 4 10 #define N 10000 11 12 int primes[N]; 13 int pflag[N]; 14 15 int is_prime(int v) 16 { 17 int i; 18 int bound = floor(sqrt(v)) + 1; 19 20 for (i = 2; i < bound; i++) { 21 /* no need to check against known composites */ 22 if (!pflag[i]) 23 continue; 24 if (v % i == 0) { 25 pflag[v] = 0; 26 return 0; 27 } 28 } 29 return (v > 1); 30 } 31 32 int main(int argn, char **argv) 33 { 34 int i; 35 int total = 0; 36 37 #ifdef _OPENMP 38 omp_set_dynamic(0); 39 omp_set_num_threads(THREADS); 40 #endif 41 42 for (i = 0; i < N; i++) { 43 pflag[i] = 1; 44 } 45 46 #pragma omp parallel for 47 for (i = 2; i < N; i++) { 48 if ( is_prime(i) ) { 49 primes[total] = i; 50 total++; 51 } 52 } 53 54 printf("Number of prime numbers between 2 and %d: %d\n", 55 N, total); 56 57 return 0; 58 }
このセクションでは、prime_pthr.c のソースコードを次に示します。
1 /* 2 * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All Rights Reserved. 3 */ 4 5 #include <stdio.h> 6 #include <math.h> 7 #include <pthread.h> 8 9 #define THREADS 4 10 #define N 10000 11 12 int primes[N]; 13 int pflag[N]; 14 int total = 0; 15 16 int is_prime(int v) 17 { 18 int i; 19 int bound = floor(sqrt(v)) + 1; 20 21 for (i = 2; i < bound; i++) { 22 /* no need to check against known composites */ 23 if (!pflag[i]) 24 continue; 25 if (v % i == 0) { 26 pflag[v] = 0; 27 return 0; 28 } 29 } 30 return (v > 1); 31 } 32 33 void * work(void *arg) 34 { 35 int start; 36 int end; 37 int i; 38 39 start = (N/THREADS) * (*(int *)arg); 40 end = start + N/THREADS; 41 for (i = start; i < end; i++) { 42 if ( is_prime(i) ) { 43 primes[total] = i; 44 total++; 45 } 46 } 47 return NULL; 48 } 49 50 int main(int argn, char **argv) 51 { 52 int i; 53 pthread_t tids[THREADS-1]; 54 55 for (i = 0; i < N; i++) { 56 pflag[i] = 1; 57 } 58 59 for (i = 0; i < THREADS-1; i++) { 60 pthread_create(&tids[i], NULL, work, (void *)&i); 61 } 62 63 i = THREADS-1; 64 work((void *)&i); 65 66 for (i = 0; i < THREADS-1; i++) { 67 pthread_join(tids[i], NULL); 68 } 69 70 printf("Number of prime numbers between 2 and %d: %d\n", 71 N, total); 72 73 return 0; 74 }
コードに競合状態が存在する場合は、メモリーアクセスの順序が定まらないため、実行するたびにその時の順序によって計算結果が異なります。prime_omp および prime_pthr プログラムの正しい答えは 1229 です。
例をコンパイルして実行できるので、prime_omp または prime_pthr を実行すると、コード内のデータの競合によって誤ったまたは矛盾した結果が生じることがわかります。
次の例では、プロンプトにコマンドを入力して、prime_omp プログラムをコンパイルし実行します。
% cc -xopenmp=noopt -o prime_omp prime_omp.c -lm % % ./prime_omp Number of prime numbers between 2 and 10000: 1229 % ./prime_omp Number of prime numbers between 2 and 10000: 1228 % ./prime_omp Number of prime numbers between 2 and 10000: 1180
次の例では、プロンプトにコマンドを入力して、prime_pthr プログラムをコンパイルし実行します。
% cc -mt -o prime_pthr prime_pthr.c -lm % % ./prime_pthr Number of prime numbers between 2 and 10000: 1140 % ./prime_pthr Number of prime numbers between 2 and 10000: 1122 % ./prime_pthr Number of prime numbers between 2 and 10000: 1141
各プログラムを 3 回実行した結果が矛盾していることに注意してください。矛盾した結果が表示されるまで、4 回以上プログラムを実行する必要がある場合もあります。
次に、データの競合が生じている位置を特定できるように、コードを計測し、実験を作成します。