4 並列プログラム

4.1 自動並列化オプション

-parallelオプションの指定により、コンパイラがソースコード内のループに対して、 自動並列化の解析を行います。

-parallel
自動並列化の指定です。

-par-threshold[n]

自動並列化のしきい値(確信度)を設定([n]は0〜100まで)
0:常に並列化
100:性能向上が確実なループのみ並列化
デフォルトは75。

例)
-par-threshold0と-par-threshold75(デフォルト)
 % cat ex9.f
 1.       program ex9
 2.       parameter(n=1000)
 3.       real a(n,n),b(n,n)
 4.       do j=1,n
 5.       do i=1,n
 6.         a(i,j)=1.
 7.         b(i,j)=1.
 8.       enddo
 9.       enddo
10.       do j=1,n
11.       do i=1,n
12.         a(i,j)=a(i,j)*b(i,j)
13.       enddo
14.       enddo
15.       do j=1,n
16.       do i=1,n
17.         print *,a(i,j)
18.       enddo
19.       enddo
20.       stop
21.       end
% ifort -parallel ex9.f
ex9.f(10) : (col. 9) remark: LOOP WAS AUTO-PARALLELIZED.

% ifort -parallel -par-threshold0 ex9.f
ex9.f(4) : (col. 9) remark: LOOP WAS AUTO-PARALLELIZED.
ex9.f(10) : (col. 9) remark: LOOP WAS AUTO-PARALLELIZED.
上記のプログラムでは、4行目と10行目のループが並列化可能であることがわかります。 -par-threshold75(デフォルト)の指定では、4行目のループは十分な処理量がなく、 性能向上が確実ではないと判断し、10行目のループだけ並列化しています。
-par-threshold0の指定では、並列化できるループは全て並列化します。
データ配置の最適化の観点からも4行目の初期化 ループは並列化されることが望ましいです。 一般的に、-par-threshold0の指定をお勧めしますが、 ループのイタレーション数が数個だけ のループでも並列化してしまいますので、ご注意下さい。

-par-report{0|1|2|3}
並列化されたループを表示し、並列化されなかったループについてはなぜ並列化されなかったかを説明します。末尾の数字が大きいほど詳細な情報を出力します。

例)
% ifort -parallel -par-report3 ex9.f
   procedure: ex9
   serial loop: line 5: not a parallel candidate due to insufficent work
   serial loop: line 4: not a parallel candidate due to insufficent work
   serial loop: line 11: not a parallel candidate due to insufficent work
   serial loop: line 16: not a parallel candidate due to statement at line 17
   serial loop: line 15: not a parallel candidate due to statement at line 17
ex9.f(10) : (col. 9) remark: LOOP WAS AUTO-PARALLELIZED.
   parallel loop: line 10
      shared     : { "a" "b" }
      private    : { "j" "i" }
      first priv.: { }
      reductions : { }

4.2 OpenMP

-openmp オプションの指定により、ソースコード内のOpenMP指示行を有効にします。

-openmp
OpenMP指示行を有効にします。

例)-openmp
 % cat ex9.f
1.       program ex9
2.       parameter(n=1000)
3.       real a(n,n),b(n,n)
4.       do j=1,n
5.       do i=1,n
6.         a(i,j)=1.
7.         b(i,j)=1.
8.       enddo
9.       enddo
10. !$OMP PARALLEL DO
11.       do j=1,n
12.       do i=1,n
13.         a(i,j)=a(i,j)*b(i,j)
14.       enddo
15.       enddo
16.       do j=1,n
17.       do i=1,n
18.         print *,a(i,j)
19.       enddo
20.       enddo
21.       stop
22.       end
% ifort -openmp ex9.f
ex9.f(10) : (col. 0) remark: OpenMP DEFINED LOOP WAS PARALLELIZED.

23 Lines Compiled
%

4.2.1 -openmpと-parallelを同時に指定したとき

-openmpと-parallelを同時に(同じソースファイルに)指定したとき、-parallelオプションはOpenMP指示行を含まないルーチンにだけ有効になります。OpenMP指示行を含むルーチンでは-openmpだけが有効です。

指定オプション
OpenMP指示行のあるルーチン
OpenMP指示行のないルーチン
-openmp
指示行のあるループのみ並列化
その他のループは並列化しない(自動並列化なし)
並列化しない
-parallel
指示行は無効
全てのループに対して自動並列化を試みる
全てのループに対して自動並列化を試みる
-openmpと-prallel
指示行のあるループのみ並列化
その他のループは並列化しない(自動並列化なし)
全てのループに対して自動並列化を試みる


例)サブルーチンcompにOpenMP指示行があるソースプログラム
     -openmpと-parallelを指定したとき

% ifort -openmp -parallel ex10.f
1.       program ex10
2.       parameter(n=1000)
3.       real a(n,n),b(n,n)
4.       do j=1,n
5.       do i=1,n
6.         a(i,j)=1.
7.         b(i,j)=1.
8.       enddo
9.       enddo
10.       call comp(a,b,n)
   … … … …
20.      subroutine comp(a,b,n)
21.      integer n
22.      real a(n,n),b(n,n)
23.    !$OMP PARALLEL DO
24.         do j=1,n
25.         do i=1,n
26.            a(i,j)=a(i,j)*b(i,j)
27.         enddo
28.         enddo
29.         do j=1,n
30.         do i=1,n
31.            b(i,j)=a(i,j)
32.         enddo
33.         enddo
34.         return
35.         end
ex10メインルーチン:4行目のループは自動並列化
compルーチン    :24行目のループは-openmpによる並列化
                 :29行目のループは並列化されない

     -openmpだけを指定したとき

 % ifort -openmp ex10.f
1.       program ex10
2.       parameter(n=1000)
3.       real a(n,n),b(n,n)
4.       do j=1,n
5.       do i=1,n
6.         a(i,j)=1.
7.         b(i,j)=1.
8.       enddo
9.       enddo
10.       call comp(a,b,n)
   … … … …
20.      subroutine comp(a,b,n)
21.      integer n
22.      real a(n,n),b(n,n)
23.    !$OMP PARALLEL DO
24.         do j=1,n
25.         do i=1,n
26.            a(i,j)=a(i,j)*b(i,j)
27.         enddo
28.         enddo
29.         do j=1,n
30.         do i=1,n
31.            b(i,j)=a(i,j)
32.         enddo
33.         enddo
34.         return
35.         end

ex10メインルーチン:4行目のループは並列化されない
compルーチン    :24行目のループは-openmpによる並列化
                :29行目のループは並列化されない

     -parallel だけを指定したとき

           
% ifort -parallel ex10.f
1.       program ex10
2.       parameter(n=1000)
3.       real a(n,n),b(n,n)
4.       do j=1,n
5.       do i=1,n
6.         a(i,j)=1.
7.         b(i,j)=1.
8.       enddo
9.       enddo
10.       call comp(a,b,n)
   … … … …
20.      subroutine comp(a,b,n)
21.      integer n
22.      real a(n,n),b(n,n)
23.    !$OMP PARALLEL DO
24.         do j=1,n
25.         do i=1,n
26.            a(i,j)=a(i,j)*b(i,j)
27.         enddo
28.         enddo
29.         do j=1,n
30.         do i=1,n
31.            b(i,j)=a(i,j)
32.         enddo
33.         enddo
34.         return
35.         end

ex10メインルーチン:4行目のループは自動並列化
compルーチン    :24行目のループは自動並列化
                :29行目のループは自動並列化
 

4.2.2 並列ループのデータ分散

Altix 4700システムでは、データ配置に「ファーストタッチ」を採用しています。ファーストタッチとは、データは、最初にそのデータにタッチ(書き込みまたは読み込み)したプロセスのローカルメモリに配置されるというものです。もし、配列(配列Aとする)を初期化しているループが並列化されていないと1CPU実行であるために、配列Aはある1つのノードに配置されます。配列Aを処理する並列実行領域では、複数のプロセッサが1つのノードへアクセスすることになります。このような状況ではプログラムの性能向上は望めません。可能なかぎり初期化ループも並列化してください。

(例) 初期化ループが1CPU実行のとき

     real*8  A(n), B(n), C(n), D(n)

    do  i=1, n
      A(i) = 0.
      B(i) = i/2
      C(i) = i/3
      D(i) = i/7
    enddo
!$omp parallel do private(i)
    do  i=1, n
    A(i) = B(i) + C(i) + D(i)
    enddo


(例) 初期化ループが並列実行のとき

     real*8  A(n), B(n), C(n), D(n)
!$omp parallel do private(i)
    do  i=1, n
      A(i) = 0.
      B(i) = i/2
      C(i) = i/3
      D(i) = i/7
    enddo
!$omp parallel do private(i)
    do  i=1, n
    A(i) = B(i) + C(i) + D(i)
    enddo

計算をしている2番目のループを並列化するときは、配列を初期化している1番目のループも並列化すれば、より性能が向上します。

4.2.3 スタックサイズ

各ユーザが使用可能なスタックサイズの上限を6GBに設定しています (メモリ使用量の上限ではありません)。 関数や手続きの中で宣言されるローカル変数がスタック領域にアロケーションされます。 上限を越えてスタックに領域を確保しようとすると、Address Errorまたは、 Segmentation Faultでプログラムは終了します。 この場合、データを静的にアロケーションするために、COMMONブロックでデータ(配列) を宣言して下さい。
環境変数KMP_STACKSIZEには自動並列化/OpneMPプログラムのスレーブスレッドが使用 できるスタック領域の最大容量を指定します。 スレーブスレッドのプライベート変数が対象です。共有変数、マスタースレッドの プライベート変数は対象ではありません。 デフォルトでは2GBが設定されています。
単位として、k(キロバイト)、m(メガバイト)、g(ギガバイト)、t(テラバイト) が使えます。

例)
% setenv KMP_STACKSIZE 4g

4.3 MPI

コマンド行の末尾にMPIライブラリのリンクを指示してください。 例)
% ifort ex_mpi.f -lmpi
実行時にはmpirunコマンドを使ってジョブを起動してください。

4.3.1 MPIのデータ型

FortranプログラムでのMPIのデータ型とそれに対応するFortran での データの型は以下の通りです。
MPI Datatype Fortran Datatype データ長(バイト)
MPI_DATATYPE_NULL 対応する型はありません -
MPI_INTEGER integer 4
MPI_REAL real 4
MPI_DOUBLE_PRECISION double precision 8
MPI_COMPLEX complex 8
MPI_DOUBLE_COMPLEX double complex 16
MPI_LOGICAL logical 4
MPI_CHARACTER character 1
MPI_INTEGER1 integer*1 1
MPI_INTEGER2 integer*2 2
MPI_INTEGER4 integer*4 4
MPI_INTEGER8 integer*8 8
MPI_REAL4 real*4 4
MPI_REAL8 real*8 8
MPI_REAL16 real*16 16
MPI_BYTE byte 1

CプログラムでのMPIのデータ型とそれに対応するCでのデータの型は以下の通りです。
MPI Datatype C Datatype データ長
MPI_CHAR char 1
MPI_SHORT short 2
MPI_INT int 4
MPI_LONG long 8
MPI_UNSIGNED_CHAR unsigned char 1
MPI_UNSIGNED_SHORT unsinged short 2
MPI_UNSIGNED unsinged int 4
MPI_UNSIGNED_LONG unsinged long 8
MPI_FLOAT float 4
MPI_DOUBLE double 8
MPI_LONG_DOUBLE long double 16
MPI_BYTE 対応する型はありません 1
MPI_PACKED 対応する型はありません -

4.3.2 サンプルプログラム

fortranプログラム
% cat simple1.f
        program simple1
        include 'mpif.h'
        call mpi_init(istat)
        call mpi_comm_size(mpi_comm_world, num_procs, ierr)
        call mpi_comm_rank(mpi_comm_world, my_proc, jerr)
        if (my_proc .eq. 0)
     &     write(6,1) 'I am process ',myproc,'.  Total number of proce
     &sses: ',num_procs
1       format(a,i1,a,i1)
        call mpi_finalize(ierr)
        stop
        end
% ifort simple1.f -lmpi

% mpirun -np 4 ./a.out
I am process 0.  Total number of processes: 4
%
Cプログラム
% cat simple1.c
#include 
#include 
main(argc, argv)
int     argc;
char    *argv[];
{
        int     num_procs;
        int     my_proc;

        MPI_Init(&argc, &argv);
        MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
        MPI_Comm_rank(MPI_COMM_WORLD, &my_proc);

        if (my_proc == 0)
                printf("I am process %d.  Total number of processes: %d\n",
                        my_proc,num_procs);
        MPI_Finalize();
}
% icc  simple1.c -lmpi
% mpirun -np 4 ./a.out
I am process 0.  Total number of processes: 4
%