姫野ベンチマーク、自動並列化、性能
商用コンパイラの自動並列化機能に関しては、今まであまりフォーカスされることはなかったような気がします。マルチコアの並列化に関しては、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の場合の実行例)
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 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 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