高速フーリエ変換 FFTE パッケージの使用 |
|
最近、特に利用される方が多くなってきている、高速フーリエ変換ライブラリである
FFTE に関して、 PGI コンパイラを使用して最適な性能を得るための方法について説明します。ここでは、特に、PGI
コンパイラ・ディレクティブ(指示行)の使用によって性能を向上させるための例を示します。また、Fortran
でコーディングされている 1 次元、 2 次元、 3 次元 FFT ライブラリとして、非常に高速であることを実証します。
この FFTE パッケージは、筑波大学の高橋大介博士の著作によるもので、HPC CHALLENGE ベンチマークの中の課題の一つとなっております。ユーザ・インタフェースが簡単ですぐに使いこなせて、高速な
FFT ルーチンと言う定評があります。このパッケージの詳細は、以下の URL にあります。
FFTE: A Fast Fourier Transform Package (RADIX-2, 3, 4, 5 AND 8 FFT ROUTINE)
本パッケージをそのままの形で PGI コンパイラを使用してコンパイルすると、ある理由からその性能が
100 % 発揮できません。ここでは、最適な性能を得るためのコンパイル・オプションとソースへのディレクティブ(指示行)の挿入方法を説明します。また、いくつかのベンチマークを行いましたので、その性能を以下に示します。ソースプログラムは、上記の
URL からダウンロードしていただき、以下の修正を加えることで PGI で最も最適な性能を提供します。なお、ここで示すベンチマークプログラムは、アーカイブの中に含まれている
speed1d.f speed2d,f speed3d.f を使用しました。また、このプログラムで組み込まれている時間計測ルーチンは、組込み関数である
ETIME を使用していますが精度が悪いため、以下のルーチンを組み込んで時間を計測しています。
時間計測ルーチン : wallclock.c(下記で述べる second() 関数)
(Elapsed timeベース:並列処理時の時間計測に便利、CPU 時間ベースではない)
KEYWORD: コンパイラ・ディレクティブ(指示行)、 高速フーリエ変換、 FFTE パッケージ、 FFT 性能
|
|
|
|
まず始めに、オリジナルの FFTE パッケージをそのまま PGI コンパイル・オプションを指定して実行モジュールを作成してみます。実行するプログラムは、アーカイブを展開した中にある
tests ディレクトリ下の speed3d.f(三次元FFT)です。この初期性能を測定します。なお、使用したシステムは、以下の表にある、AMD
のプロセッサとインテル(R) のデュアル・プロセッサを有する二つのシステムです。
|
FFTE 性能測定に用いたシステム
|
AMD Dual Core プロセッサ |
インテル(R) Dual Core プロセッサ |
| システム名 |
White Box |
EPSON Endeavor Pro3300 |
| プロセッサ |
AMD Athlon64X2 4400+
|
Pentium® D 820 |
| Clock |
2.2 GHz |
2.8 GHz |
| L2 cache |
1MB + 1MB |
1MB + 1MB |
| 使用メモリ(最大帯域) |
DDR400 PC3200 CL3 3-3-3 (6.4GB/s)
512MB x 2 枚(バルク製品) |
DDR2-667 (10.6GB/s) 1GB x 2 枚 |
| STREAM メモリ実効帯域(SSEベクトル処理付) |
4290 MB/sec |
4863 MB/sec |
STREAM メモリ実効帯域
(ベクトル処理なし) |
2551 MB/sec |
3583 MB/sec |
| 使用 OS |
SUSE 10.0 (kernel 2.6.13) |
SUSE 10.0 (kernel 2.6.13) |
| 64ビット環境 |
AMD64 |
EM64T |
| 使用コンパイラ |
PGI 6.1 |
PGI 6.1 |
|
|
Makefile定義するコンパイル・オプション並びに パッケージで使用されているヘッダーファイル
param.h は、以下の通りです。なお、64ビットアドレッシングに対応するため、コンパイル・オプションには
-mcmodel=medium を付加しています。32bit システムの場合は必要ありません。また、param.h
の中のパラメータ NP は、プロセッサ特性で NP の値が変わりますが、プロセッサの如何に拘らず、4
あるいは 8 を指定して予め性能を評価することをお勧めします。
|
【シリアル計算時のオプション】
F77 = pgf90
FFLAGS = -fastsse -Minfo -Mvect=nosizelimit -mcmodel=medium -Mprefetch=distance:8,nta -I..
LDFLAGS=
【OpenMP 並列計算時のオプション】
F77 = pgf90
FFLAGS = -fastsse -Minfo -Mvect=nosizelimit -mcmodel=medium -Mprefetch=distance:8,nta -mp -I..
LDFLAGS=
【param.hファイル】
AMD Athlon64x2 プロセッサの場合 : PARAMETER (NBLK=16), PARAMETER (NP=4)
Intel(R) Pentium(R) D プロセッサの場合 : PARAMETER (NBLK=16), PARAMETER (NP=8)
Cache Size of L2 cache
PARAMETER (L2SIZE=1048576) キャッシュサイズの指定
【speed3d.fの時間関数変更:オリジナルは ETIME 関数】
REAL*8 second
---------------
TIME1=second()
DO I=1,LOOP
CALL ZFFT3D(A,NX,NY,NZ,-1)
END DO
TIME2=second()
TIME0=TIME2-TIME1
|
表1 FFTE speed3d オリジナル性能 (3次元 256**3)
| システム |
クロック |
シリアル
(MFLOPS) |
OpenMP
1スレッド
(MFLOPS) |
OpenMP
2スレッド
(MFLOPS) |
並列
効率 |
| Athlon64x2 |
2.2GHz |
1132
(1.78秒) |
842
(2.39秒) |
1561
(1.28秒) |
1.85 |
| Pentium(R)D |
2.8GHz |
1235
(1.63秒) |
908
(2.21秒) |
1526
(1.31秒) |
1.68 |
|
|
上記の表の中で問題となる点は、シリアル実行用にコンパイルした実行モジュールの性能とOpenMP
並列モジュールの1スレッドの性能差が大きいことです。これは、一概には言えませんが何らかのコンパイル最適化等の問題があることをほのめかす事実として、調査を行ってみました。
|
| PGI コンパイラで最適な性能を得る(コンパイラ・ディレクティブを挿入) |
|
|
|
表 1 の性能は、このままでも良い性能であることは確かですが、上記で述べた問題は、何らかの最適化処理によるものと考え、プログラム内の精査を行いました。結論から述べると、この問題は
PGI コンパイラは、より高度な最適化処理を施してしまったため、オリジナルソースレベルで考慮されている手動での最適化技術を無駄なものにしてしまっていることでした。本来は、コンパイラによる最適化処理を行った方が良いのが一般的な考え方ですが、この
FFTE パッケージの著者である高橋氏は、ソースレベルでキャッシュ最適化(キャッシュタイリング、ストリップマイニング)を行っているため、このソースレベルにコンパイラがさらに最適化を施した場合、著作者が考えているソースレベル最適化のイメージを壊すことになってしまいます。この
FFTE パッケージは、このようなコーディングレベルでのキャッシュ活用最適化を施しているため、性能の良い、さらにポータビリティの良い
FFT パッケージになっています。今回の性能の問題は、結果的にこのような箇所に
PGI コンパイラがより高度な最適化を施してしまったために生じたものであり、本来のコンパイラ機能とは全く逆の作用を及ぼしてしまったと言うことになります。言ってみれば、コンパイラが余計なお世話をしたことになります。
以下に、zfft3d.f のソース内の該当する最適化部分をリストします。また、リストの下部に「コンパイル最適化メッセージ」を表示しています。ここでは、行番号
85、95 の周辺部の最適化により、コンパイラがループ構造を再構成した旨のメッセージが現れています。これは、本来、著作者が意図した最適化イメージではありません。従って、コンパイラ・ディレクティブを挿入して、以下の該当部分のループの最適化を抑止します。
|
C (C) COPYRIGHT SOFTWARE, 2000-2004, ALL RIGHTS RESERVED
C BY
C DAISUKE TAKAHASHI
zfft3d0: オリジナルソース(zfft3d.f)
( 80) !$OMP DO
( 81) DO 80 J=1,NY
( 82) DO 70 II=1,NX,NBLK
( 83) DO 30 KK=1,NZ,NBLK
( 84) DO 20 I=II,MIN0(II+NBLK-1,NX)
( 85) DO 10 K=KK,MIN0(KK+NBLK-1,NZ)
( 86) BZ(K,I-II+1)=A(I,J,K)
( 87) 10 CONTINUE
( 88) 20 CONTINUE
( 89) 30 CONTINUE
( 90) DO 40 I=II,MIN0(II+NBLK-1,NX)
( 91) CALL FFT235(BZ(1,I-II+1),C,WZ,NZ,LNZ)
( 92) 40 CONTINUE
( 93) DO 60 K=1,NZ
( 94) DO 50 I=II,MIN0(II+NBLK-1,NX)
( 95) A(I,J,K)=BZ(K,I-II+1)
( 96) 50 CONTINUE
( 97) 60 CONTINUE
( 98) 70 CONTINUE
( 99) 80 CONTINUE
zfft3d0: コンパイルメッセージ
84, Distributing loop; 2 new loops (この最適化は本来行って欲しくない)
Interchange produces reordered loop nest: 85, 84
Loop unrolled 4 times
93, Interchange produces reordered loop nest: 94, 93
Loop unrolled 4 times
|
|
● PGI コンパイラ・ディレクティブを挿入してコンパイラによる高度最適化を抑止する
|
|
コンパイラによる(ベクトル)最適化を抑止するためのディレクティブとして、cpgi$ novector が用意されています。このディレクティブを以下に示すように該当する部分に挿入します。PGI
では、その他各種のディレクティブが用意されておりますが、この詳細は、 PGI
USER'S GUIDE をご参照ください。なお、 cpgi$l の l (エル) は、次の行の「ループ(loop)」のみに適用すると言う意味となります。このディレクティブを挿入してコンパイルすると、以下のように、コンパイルメッセージは、「86,
Loop unrolled 4 times」のみとなります。
|
zfft3d0: オリジナルソース(zfft3d.f) にベクトル最適化抑止ディレクティブを挿入する
( 80) !$OMP DO
( 81) DO 80 J=1,NY
( 82) DO 70 II=1,NX,NBLK
( 83) DO 30 KK=1,NZ,NBLK
( 84) cpgi$l novector
( 85) DO 20 I=II,MIN0(II+NBLK-1,NX)
( 86) DO 10 K=KK,MIN0(KK+NBLK-1,NZ)
( 87) BZ(K,I-II+1)=A(I,J,K)
( 88) 10 CONTINUE
( 89) 20 CONTINUE
( 90) 30 CONTINUE
( 91) DO 40 I=II,MIN0(II+NBLK-1,NX)
( 92) CALL FFT235(BZ(1,I-II+1),C,WZ,NZ,LNZ)
( 93) 40 CONTINUE
( 94) cpgi$l novector
( 95) DO 60 K=1,NZ
( 96) DO 50 I=II,MIN0(II+NBLK-1,NX)
( 97) A(I,J,K)=BZ(K,I-II+1)
( 98) 50 CONTINUE
( 99) 60 CONTINUE
( 100) 70 CONTINUE
( 101) 80 CONTINUE
( 102) !$OMP DO
zfft3d0: コンパイルメッセージ
86, Loop unrolled 4 times
96, Loop unrolled 4 times
|
|
zfft3d.f の中には、さらに同じようにディレクティブの挿入を行う箇所があります。
|
( 103) DO 170 K=1,NZ
( 104) DO 150 II=1,NX,NBLK
( 105) DO 110 JJ=1,NY,NBLK
( 106) cpgi$l novector
( 107) DO 100 I=II,MIN0(II+NBLK-1,NX)
( 108) DO 90 J=JJ,MIN0(JJ+NBLK-1,NY)
( 109) BY(J,I-II+1)=A(I,J,K)
( 110) 90 CONTINUE
( 111) 100 CONTINUE
( 112) 110 CONTINUE
( 113) DO 120 I=II,MIN0(II+NBLK-1,NX)
( 114) CALL FFT235(BY(1,I-II+1),C,WY,NY,LNY)
( 115) 120 CONTINUE
( 116) cpgi$l novector
( 117) DO 140 J=1,NY
( 118) DO 130 I=II,MIN0(II+NBLK-1,NX)
( 119) A(I,J,K)=BY(J,I-II+1)
( 120) 130 CONTINUE
( 121) 140 CONTINUE
( 122) 150 CONTINUE
( 123) DO 160 J=1,NY
( 124) CALL FFT235(A(1,J,K),C,WX,NX,LNX)
( 125) 160 CONTINUE
( 126) 170 CONTINUE
|
|
● zfft1d.f 並びに zfft2d.f に関しても同じようにディレクティブを挿入
|
【zfft1d.fの挿入箇所】
( 105) !$OMP DO PRIVATE(IJ,IJ0,IR,J,TEMP)
( 106) DO 110 II=1,N1,NBLK
( 107) DO 30 JJ=1,N2,NBLK
( 108) cpgi$l novector
( 109) DO 20 I=II,MIN0(II+NBLK-1,N1)
( 110) DO 10 J=JJ,MIN0(JJ+NBLK-1,N2)
( 111) C(J,I-II+1)=A1(I,J)
( 112) 10 CONTINUE
( 113) 20 CONTINUE
( 114) 30 CONTINUE
(中略)
( 146) !$OMP DO
( 147) DO 150 JJ=1,N2,NBLK
( 148) DO 120 J=JJ,MIN0(JJ+NBLK-1,N2)
( 149) CALL FFT235(B(1,J),C,W1,N1,IP1)
( 150) 120 CONTINUE
( 151) cpgi$l novector
( 152) DO 140 I=1,N1
( 153) DO 130 J=JJ,MIN0(JJ+NBLK-1,N2)
( 154) A2(J,I)=B(I,J)
( 155) 130 CONTINUE
( 156) 140 CONTINUE
( 157) 150 CONTINUE
【zfft2d.fの挿入箇所】
( 77) !$OMP DO
( 78) DO 70 II=1,NX,NBLK
( 79) DO 30 JJ=1,NY,NBLK
( 80) cpgi$l novector
( 81) DO 20 I=II,MIN0(II+NBLK-1,NX)
( 82) DO 10 J=JJ,MIN0(JJ+NBLK-1,NY)
( 83) B(J,I-II+1)=A(I,J)
( 84) 10 CONTINUE
( 85) 20 CONTINUE
( 86) 30 CONTINUE
( 87) DO 40 I=II,MIN0(II+NBLK-1,NX)
( 88) CALL FFT235(B(1,I-II+1),C,WY,NY,LNY)
( 89) 40 CONTINUE
( 90) cpgi$l novector
( 91) DO 60 J=1,NY
( 92) DO 50 I=II,MIN0(II+NBLK-1,NX)
( 93) A(I,J)=B(J,I-II+1)
( 94) 50 CONTINUE
( 95) 60 CONTINUE
( 96) 70 CONTINUE
( 97) !$OMP DO
|
|
| PGI コンパイラでの FFTE の最適なオプション |
|
|
FFTE パッケージをコンパイルする際の性能最適なコンパイル・オプションを以下に示します。
以下のオプションの中で、-Mvect=nosizelimit は必ず付加してください。これは、ループ内のステートメントが多い箇所があり、PGI のデフォルトのベクトル最適化の対象とする閾値を超えるものがあり、このオプションを指定することにより、全てのループを最適化の対象として扱います。このオプションを付けるだけでも性能が大きく向上します。また、3次元FFT
で、データサイズが大きくなり 64ビットアドレッシングが必要な場合は、必ず、-mcmodel=medium オプションを付加してください。さらに、OpenMP による並列化モジュールの作成を行うためには、
-mp オプションを付加してください。(PGI 6.2より、-Mvect=nosizelimit がデフォルトとなりましたので、PGI
6.2 以降では指定する必要がありません)
|
【FFTE の最適なオプション】
FFLAGS = -fastsse -Minfo -Mvect=nosizelimit -Mprefetch=distance:8,nta -Minline=size:50
(OpenMP並列の場合)
FFLAGS = -fastsse -Minfo -Mvect=nosizelimit -Mprefetch=distance:8,nta -Minline=size:50 -mp
|
|
| 高速フーリエ変換 FFTE の性能 |
|
|
上記で述べた最適化を施した後、 FFTE の 3 次元 FFT (256**3) の性能は、以下のようにオリジナルをそのままコンパイルして得た性能(表1)に較べて、OpenMP
の性能が大きく向上しました。特に、2スレッド並列性能は、オリジナルのものに較べて最大
1.57 倍向上したことが分かります。
|
表 2 FFTE speed3d PGI 最適化性能 (3次元 256**3)
| システム |
クロック |
シリアル
(MFLOPS) |
OpenMP
1スレッド
(MFLOPS) |
OpenMP
2スレッド
(MFLOPS) |
並列
効率 |
| Athlon64x2 |
2.2GHz |
1132
(1.78秒) |
1154
(1.75秒) |
2200
(0.91秒) |
1.90 |
| Pentium(R)D |
2.8GHz |
1235
(1.63秒) |
1341
(1.55秒) |
2398
(0.83秒) |
1.78 |
|
|
以下の表では、三次元、二次元 FFT のデータサイズを変更して性能測定をした結果を示しました。プロセッサは、インテル(R)とAMDのプロセッサの二つのケースを示しました。これらのプロセッサ(L2
chache size 1MB)においては、128データサイズの場合が最適高速な性能を得られることが分かります。3次元データのFFTでは、プロセッサのクロック値がその性能に反映される傾向があります。一方、二次元
FFT では、、クロックの低い Athlon64x2 の性能は、Pentium(R) Dの性能とほぼ同じと言う結果が得られています。このように、扱うデータサイズによっても絶対性能が異なるため、一概にプロセッサの種類、クロック値で、性能が速い・遅いを議論できないことがご理解いただけることでしょう。
以下の結果は、一つのメモリ帯域を共有する「デュアルコア」プロセッサ上でのスレッド並列性能ですので、以下のような並列効率が得られるソースベースのライブラリは驚きに値するものです。従って、FFT
を使用しているプログラムにおいては、OpenMP対応の FFTE パッケージを使用することで、大きな性能向上を得ることができます。
表 3 三次元 FFTE 計算の性能 (OpenMP)
(Pentium(R) D : 2.8GHz) 単位 MFLOPS
| Size ( **3) |
64 |
128 |
256 |
| 1スレッド (MFLOPS) |
1283 |
1475 |
1341 |
| 2スレッド (MFLOPS) |
2432 |
2733 |
2398 |
| 並列効率 |
1.89 |
1.85 |
1.78 |
表 4 三次元 FFTE 計算の性能 (OpenMP)
(Athlon64x2 : 2.2GHz) 単位 MFLOPS
| Size ( **3) |
64 |
128 |
256 |
| 1スレッド (MFLOPS) |
1123 |
1198 |
1154 |
| 2スレッド (MFLOPS) |
2124 |
2315 |
2200 |
| 並列効率 |
1.89 |
1.93 |
1.90 |
表 5 二次元 FFTE 計算の性能 (OpenMP)
(Pentium(R) D : 2.8GHz) 単位 MFLOPS
| Size ( **2) |
64 |
128 |
256 |
512 |
| 1スレッド (MFLOPS) |
1705 |
1735 |
1393 |
1179 |
| 2スレッド (MFLOPS) |
2330 |
2627 |
2413 |
2017 |
| 並列効率 |
1.37 |
1.51 |
1.73 |
1.71 |
表 6 二次元 FFTE 計算の性能 (OpenMP)
(Athlon64x2 : 2.2GHz) 単位 MFLOPS
| Size ( **2) |
64 |
128 |
256 |
512 |
| 1スレッド (MFLOPS) |
1708 |
1751 |
1417 |
1197 |
| 2スレッド (MFLOPS) |
2278 |
2653 |
2368 |
1891 |
| 並列効率 |
1.34 |
1.52 |
1.61 |
1.56 |
|
|