ソフテック・トップページへ
ホーム 製品 セキュリティ・サービス HPCサービス ダウンロード 企業情報

PGI compiler Tutorial
高速フーリエ変換 FFTE パッケージを使う
PGI チュートリアル > FFTE ライブラリ

 
高速フーリエ変換 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 オリジナルをコンパイルして得られる性能

まず始めに、オリジナルの 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

この画面トップへ

技術情報 TIPS トップに戻る >>




 ソフテックは、PGI 製品の公認正規代理店です

サイトマップ お問合せ
Copyright 2004-2006 SofTek Systems Inc. All Rights Reserved.