PGI コンパイラの自動並列化による性能について

キーワード 姫野ベンチマーク、自動並列化、性能

 商用コンパイラの自動並列化機能に関しては、今まであまりフォーカスされることはなかったような気がします。マルチコアの並列化に関しては、OpenMP によるディレクティブ、プラグマでスレッド並列化を明示的に行うのが一般的ですが、商用コンパイラには、自動並列化機能も備わっています。この自動並列化の能力は、商用コンパイラによっても異なるようです。ここでは、PGI コンパイラの例をご紹介しますが、かなり優れた性能を得ることができました。これは、PGIコンパイラの自動並列抽出能力と並列実装能力の高さを物語っています。是非、一度は、今お持ちのインテルコンパイラなどの商用コンパイラで、実際の自動並列化能力を評価してみてください。
2010年3月18日 Copyright © 株式会社ソフテック 加藤

性能実測するシステム環境について

 まず始めに、性能実測に使用する私のテストマシン環境について説明します。

ホスト側プロセッサ Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz x 1 プロセッサ
ホスト側メモリ PC3-10600(DDR3-1333)2GB x 2 枚
ホスト側 OS Cent OS 5.4、カーネル 2.6.18-164.el5
PGI コンパイラ PGI 10.3 (PGI 2010)

 Core i7 のプロセッサ(4コア)を1個積んでいる単純な 4GBメモリの CentOS 5.4 の Linux マシンです。メモリ性能は、2枚のメモリモジュールしか実装していないため、最適なメモリモジュール配置構成を有していません。その結果、理想的な実効メモリ帯域ではありませんが、16GB/sec 程の帯域を有しています

1 OpenMP スレッド Triad: 14780.6029 (MB/s)
2 OpenMP スレッド Triad: 16612.4756 (MB/s)
4 OpenMP スレッド Triad: 15955.8867 (MB/s)

姫野ベンチマークの「シリアル版」プログラムを自動並列化でチェック

 姫野ベンチマークには、OpenMP 版のソースコードも提供されていますが、あえてシリアル実行用のソースを「自動並列化」して、その並列性能を見てみます。ここで使用するプログラムは、「姫野ベンチマークの」のホームページから入手可能です。実行するデータサイズは、全て M サイズとします。

PGIコンパイラの自動並列化オプション

 PGIの自動並列化を行うためのオプションは、-Mconcur を付加することで可能となります。コンパイラは、プログラム内のループ構造の中に並列性を確認し、データの依存性がない場合、スレッド並列化を行います。-Mconcur オプションとそのサブオプションの詳細は、こちらのページに説明しています。並列化を適用したかどうかの確認は、-Minfo=par オプションでコンパイル・メッセージを見ることにより可能です。

● 自動並列化オプション -Mconcur
  $ pgf77     -Mconcur -fastsse -Minfo=par ****.f   (FORTRAN77専用)
  $ pgfortran -Mconcur -fastsse -Minfo=par ****.f   (Fortran77/90/95/2003)
  $ pgcc      -Mconcur -fastsse -Minfo=par *****.c  (C11)
  $ pgcpp     -Mconcur -fastsse -Minfo=par ****.cpp (C++)

自動並列化コードの実行方法

 自動並列化されたコードは、OpenMP と同じ、いわゆるスレッド並列化コードですので、OMP_NUM_THREADS 環境変数または、NCPUS 環境変数に実行に使用する並列スレッド数を指定した後に、実行モジュールを実行することで並列実行できます。

  $ export OMP_NUM_THREADS=4 (4-core を使って、4スレッド並列実行する)
  $ export NCPUS=4 (PGIの場合は、NCPUS への設定でも同じ効果があります)
  $ ./a.out (Linux, OS Xの場合の実行例) 
  $ HimenoBMT.exe (Windowsの場合の実行例)

Fortran90 (Dynamic allocation版)の自動並列化性能評価

 Fortran 90版の姫野ベンチマークコードをコンパイルしてみましょう。コンパイルメッセージは、「並列化に関するメッセージ」を出すために、-Minfo=par を指定しています。以下のようなメッセージが出力されます。並列化できた部分、できなかった部分とその理由等を知らせてくれます。以下は、コンパイルメッセージの例です。また、その後に、ソースコードの抜粋も示しました。姫野ベンチマークでは、293行目のループ・ブロックがCPU時間のホットスポットですが、この部分は、コンパイルメッセージにも表れているように自動並列化されています。さらに、メッセージには、ループの回数(trip count) が 33 以上の場合に並列実行する旨が表示されています。ループの回数によって並列実行を行うかどうかを設定することを「スレッシュホールド(閾値)」を設定すると言いますが、並列効果が出る十分な計算時間がないと想定される場合は、シリアル実行するような内部コードを生成しています。PGIコンパイラは、こうしたことも自動で行います。スレッシュホールド値を明示的に指定したい場合は、-Mconcur=altcode:33 と言うサブオプションを指定することもできます。また、以下のコンパイルの例では、-Mconcur=bind と言う bind サブオプションを付けていますが、この意味は、プロセッサコアとスレッドをバインドし固定すると言う意味です。この例では、一対一にスレッドをコアに固定して実行させています。

【自動並列化コンパイルメッセージ】
[kato@photon29 Himeno]$ pgf90 -fastsse -Minfo=par -Mconcur=bind himenoBMTxp.f90
initmt:
    235, Loop not parallelized: may not be beneficial
    236, Loop not parallelized: may not be beneficial
    237, Loop not parallelized: may not be beneficial
    238, Loop not parallelized: innermost
    239, Loop not parallelized: innermost
    240, Loop not parallelized: innermost
    241, Loop not parallelized: innermost
    243, Loop not vectorized/parallelized: loop count too small
         Parallel code generated with block distribution if trip count is greater than or equal to 50
    244, Parallel code generated with block distribution if trip count is greater than or equal to 33
    245, Parallel code generated with block distribution if trip count is greater than or equal to 33
    248, Loop not parallelized: innermost
jacobi:
    293, Parallel code generated with block distribution if trip count is greater than or equal to 33
    315, Parallel code generated with block distribution if trip count is greater than or equal to 50

    				PGF90 (Version     10.3)          03/18/2010  06:18:06      page 6

(  285)   integer,intent(in) :: nn
(  286)   real(4),intent(inout) :: gosa
(  287)   integer :: i,j,k,loop
(  288)   real(4) :: s0,ss
(  289) !
(  290) !
(  291)   do loop=1,nn
(  292)      gosa= 0.0
(  293)      do k=2,kmax-1
(  294)         do j=2,jmax-1
(  295)            do i=2,imax-1
(  296)               s0=a(I,J,K,1)*p(I+1,J,K) &
(  297)                    +a(I,J,K,2)*p(I,J+1,K) &
(  298)                    +a(I,J,K,3)*p(I,J,K+1) &
(  299)                    +b(I,J,K,1)*(p(I+1,J+1,K)-p(I+1,J-1,K) &
(  300)                                -p(I-1,J+1,K)+p(I-1,J-1,K)) &
(  301)                    +b(I,J,K,2)*(p(I,J+1,K+1)-p(I,J-1,K+1) &
(  302)                                -p(I,J+1,K-1)+p(I,J-1,K-1)) &
(  303)                    +b(I,J,K,3)*(p(I+1,J,K+1)-p(I-1,J,K+1) &
(  304)                                -p(I+1,J,K-1)+p(I-1,J,K-1)) &
(  305)                    +c(I,J,K,1)*p(I-1,J,K) &
(  306)                    +c(I,J,K,2)*p(I,J-1,K) &
(  307)                    +c(I,J,K,3)*p(I,J,K-1)+wrk1(I,J,K)
(  308)               ss=(s0*a(I,J,K,4)-p(I,J,K))*bnd(I,J,K)
(  309)               GOSA=GOSA+SS*SS
(  310)               wrk2(I,J,K)=p(I,J,K)+OMEGA *SS
(  311)            enddo
(  312)         enddo
(  313)      enddo

自動並列実行

 姫野ベンチマークは、メモリの帯域を全て使い尽くすため、並列性能もメモリの帯域性能に支配されます。以下は、4スレッドで実行した並列実効性能を示したものです。約 6580 MFLOPS の性能を「自動並列化」によって達成しています。この性能は、実は、明示的なOpenMPのディレクティブを入れたものと同等か、それ以上の性能を記録しています。かなり優れた性能と言うことができます。これは、PGIコンパイラの自動並列抽出能力と並列実装能力の高さを物語っています。また、PGIコンパイラのスレッド並列用内部コードのオーバーヘッドは小さいと言う定評があります。自動並列化は、コンパイラ自身が何ら外部インタフェース無しにネイティブにコード化できるため、同じプログラムの OpenMP 版よりも速くなる場合があります。

[kato@photon29 NEW]$ export OMP_NUM_THREADS=4
[kato@photon29 NEW]$ a.out
 Select Grid-size:
 Grid-size=
            XS (64x32x32)
            S  (128x64x64)
            M  (256x128x128)
            L  (512x256x256)
            XL (1024x512x512)
m
  mimax=          257  mjmax=          129  mkmax=          129
  imax=          256  jmax=          128  kmax=          128
  Time measurement accuracy : .10000E-05
  Start rehearsal measurement process.
  Measure the performance in 3 times.
   MFLOPS:    6485.476       time(s):   6.3420999999999991E-002   1.6938397E-03
 Now, start the actual measurement process.
 The loop will be excuted in         2838  times.
 This will take about one minute.
 Wait for a while.
  Loop executed for          2838  times
  Gosa :   3.4766761E-04
  MFLOPS:    6581.833       time(s):    59.11793300000000
  Score based on Pentium III 600MHz :    79.45236	

OpenMP並列実行

 姫野ベンチマークの Fortran 90 OpenMP 版を同じように実行して、自動並列化モジュールの性能と較べてみましょう。以下に、コンパイル結果、4スレッド実行結果を示しました。6512 MFLOPSが記録されています。自動並列の結果に較べて若干性能が落ちていますが、まあ、同じような性能と言うことができます。

【OpenMP版ソースのコンパイルメッセージ】
[kato@photon29 OMP]$ pgf90 -fastsse -Minfo=mp -mp himenoBMTxp_omp.f90
jacobi:
    293, Parallel region activated
    296, Barrier
    297, Begin master region
    299, End master region
    302, Parallel loop activated with  runtime schedule schedule
    324, Barrier
    326, Parallel loop activated with static block schedule
    331, Begin critical section (__cs_unspc)
         End critical section (__cs_unspc)
    334, Parallel region terminated
    
【4スレッド並列実行】
[kato@photon29 OMP]$ a.out
 For example:
 Grid-size=
            XS (64x32x32)
            S  (128x64x64)
            M  (256x128x128)
            L  (512x256x256)
            XL (1024x512x512)
  Grid-size =
m
  mimax=          257  mjmax=          129  mkmax=          129
  imax=          256  jmax=          128  kmax=          128
  Time measurement accuracy : .10000E-05
  Start rehearsal measurement process.
  Measure the performance in 3 times.
   MFLOPS:    6008.025       time(s):   6.8460999999999994E-002   1.6938582E-03
 Now, start the actual measurement process.
 The loop will be excuted in         2629  times.
 This will take about one minute.
 Wait for a while.
  Loop executed for          2629  times
  Gosa :   3.7734280E-04
  MFLOPS:    6512.016       time(s):    55.35142800000000
  Score based on Pentium III 600MHz :    78.60957

C プログラム(static allocate版)の自動並列化性能評価

 姫野ベンチマークの C static allocation 版 himenoBMTxps.c の自動並列化を行ってみます。このプログラムは、使用する配列を静的にアロケーションしているものです。一般に、「静的アロケーションプログラム」は、「動的なアロケーション」で構成するプログラムより性能が良いのが普通です。また、コンパイラにとっても、静的配列構成は、ポインタ等による変数間の繋がりが見えにくいものとは違い、自動で並列性を抽出することが容易です。以下にその結果を紹介します。4スレッド並列で、6770 MFLOPS の性能を記録しています。これは、配列の「静的アロケーション」による性能の向上によるものです。

● オリジナルプログラム himenoBMTxps.c の修正
  関数プロトタイプ float jacobi(); を
                   float jacobi(int); に変更
  データサイズを指定するため -DMIDDLEを指定する。
【自動並列化コンパイルメッセージ】
[kato@photon29 C_static]$ pgcc -fastsse -O3 -Minfo=par -Mconcur=bind -DMIDDLE himenoBMTxps.c
initmt:
    152, Parallel code generated with block distribution
    170, Parallel code generated with block distribution if trip count is greater than or equal to 33
jacobi:
    198, Parallel code generated with block distribution if trip count is greater than or equal to 33
    223, Parallel code generated with block distribution if trip count is greater than or equal to 50
kato@photon29 C_static]$ a.out
mimax = 129 mjmax = 129 mkmax = 257
imax = 128 jmax = 128 kmax =256
 Start rehearsal measurement process.
 Measure the performance in 3 times.

 MFLOPS: 6708.125253 time(s): 0.061316 1.693554e-03

 Now, start the actual measurement process.
 The loop will be excuted in 2935 times
 This will take about one minute.
 Wait for a while

 Loop executed for 2935 times
 Gosa : 3.347842e-04
 MFLOPS measured : 6770.976028  cpu : 59.430660
 Score based on Pentium III 600MHz : 82.572878

C プログラム(dynamic allocate版)の自動並列化

 姫野ベンチマークの C dynamic allocation 版 himenoBMTxps.c の自動並列化を行ってみます。このプログラムは、「動的なアロケーション」で構成するプログラムです。ポインタによる変数・配列の連携を行っていますので、コンパイラには、その連携間にデータコンフリクトがあるかどうか不明となる部分が出てきます。従って、並列化以前の対応として、プログラム内に明示的な restrict修飾子の指定、あるいは、PGIオプション -Msafeptr を指定してコンパイルして、依存性がないことを指示する必要があります。

 

 このプログラムは、結果的に自動並列化はできません。ポインタの多用されたプログラムのために、データフロー解析において並列性の抽出が困難であったという結論です。他社のコンパイラでも、これは同じ状況であると思います。Cプログラムの本質とも言うべき「ポインタの利用」が、こうした並列性の抽出において障害となっていると言う例となります。

● オリジナルプログラム himenoBMTxpa.c の修正
以下の Include の定義と関数プロトタイプを指定して下さい。
#include <malloc.h>
void *malloc(size_t size);
【自動並列化コンパイルメッセージ】
[kato@photon29 C_dynamic]$ make
pgcc -c -fastsse -Minfo -Msafeptr -Mconcur himenoBMTxpa.c
mat_set:
    263, Parallel code generated with block distribution if trip count is greater than or equal to 50
    265, Memory set idiom, loop replaced by call to __c_mset4
mat_set_init:
    275, Parallel code generated with block distribution if trip count is greater than or equal to 50
    277, Memory set idiom, loop replaced by call to __c_mset4
jacobi:
    293, Loop not vectorized/parallelized: too deeply nested
    298, Loop not parallelized: may not be beneficial 自動並列化できず
         Generated 4 alternate loops for the loop
         Generated vector sse code for the loop
         Generated 8 prefetch instructions for the loop
    322, Parallel code generated with block distribution if trip count is greater than or equal to 50
    324, Memory copy idiom, loop replaced by call to __c_mcopy4
pgcc -o a.out himenoBMTxpa.o -fastsse -Minfo -Msafeptr -Mconcur=bind