インテル® Fortran Studio XE によるマンデルブロー描画プログラムの高速化

インテル® Fortran コンパイラー

1. はじめに

インテル® Fortran Studio XE 2011 (Windows* 版および Linux* 版) は、ソフトウェア開発用言語として Fortran を採用している開発者を対象とするソフトウェア開発スイートです。インテル® (Visual) Fortran Composer XE 2011 の他、インテル® Inspector XE、および インテル® VTune™ Amplifier XE から構成され、プログラム開発における各フェーズでこれらのツールを利用することで、信頼性に優れ十分に最適化されたプログラムを効率的に開発することができます。

インテル® Visual Fortran Composer XE 2011 Windows 版 (インテル® Fortran Composer XE 2011 Linux 版)

高レベル最適化(HLO)に加え、高度なベクトル化、並列化などのプログラム最適化のためのさまざまな機能を提供するインテル® Fortran コンパイラー、および BLAS、LAPACK、ScaLAPACK、あるいは FFT など、広範囲におよぶ高度に最適化された数値演算ルーチンを提供するインテル® MKL ライブラリーから構成されます。

また、Windows 版では OpenMP* で並列化されたプログラムのデバッグを容易にするインテル® Parallel Debugger Extension が、Linux 版では GUI (グラフィカル・ユーザー・インターフェース) からデバッグ作業を行える Intel® デバッガー(IDB) も同梱されています。Windows 環境では統合機能をインストールすることによって、Microsoft* Visual Studio* を使用して開発作業を行えます(注1.)。

インテル® Inspector XE (Windows 版、Linux 版)

メモリーあるいはスレッドに関連するアプリケーションの問題を動的に分析するツールです。メモリー・リークなどのメモリーに関連する問題や、データ競合あるいはデッドロックといったスレッドに関連する問題を、分析対象プログラムの実行中に特定します。Fortran の他にも C、C++、C++/CLI、あるいは C# で記述された実行コードの分析に対応しています。最近のバージョンでは MPI アプリケーションの分析もサポートされるようになりました(注2.)。Microsoft Visual Studio (Windows 版)、スタンドアローンGUI、あるいはコマンドラインから使用することができます。

インテル® VTune™ Amplifier XE (Windows 版、Linux 版)

ハードウェア・イベントベース・サンプリングあるいはユーザーモード・サンプリングを使用して、プログラムの実行に時間がかかっている部分(hotspot)やその原因を分析することができるパフォーマンス分析用ツールです。用意されているいくつかの分析タイプの他に、必要であれば新たな分析タイプを定義することもできます。Fortran、C/C++、あるいは C# で作成されたプログラムの分析に対応しています。OpenMP などを利用して並列化されているプログラムの分析にも使用でき、MPI アプリケーションの分析もサポートされています。インテル® Inspector XE と同様に、Microsoft Visual Studio (Windows 版)、スタンドアローンGUI、あるいはコマンドラインから使用することができます。

今回の記事ではマンデルブロー集合を描画するプログラムを取り上げ、インテル® Fortran Studio XE により提供されているこれらのツールを使用して、プログラム実行速度の改善を試みます。インテル® Fortran Composer XE 2011 の代わりにインテル® C++ Composer XE 2011 が同梱されているインテル® C++ Studio XE 2011、あるいは両方のコンパイラー製品が同梱されているインテル® Parallel Studio XE 2011 も用意されていますが、それらの製品を利用する場合でもこの記事の内容は参考になるはずです。

2. マンデルブロー描画ルーチン

マンデルブロー集合

フラクタル図形としてよく知られているマンデルブロー集合(図1.)は、数学的に複素数の漸化式として定義されます。今回利用するプログラムでは、サブルーチン mandelbrot_calc において実数の演算に展開された漸化式の計算を行っています。

図 1. マンデルブロー集合 [-2.0, 1.0] x [-1.2, 1.2]、最大反復回数 5000、サイズ1280×1024

サブルーチン mandelbrot_calc を呼び出して出力画像の各ピクセル・データを作成するためのコードがリスト1. になります。別に定義した線形補完関数 lerp を使用して、画像 (サイズ image_w、image_h) の各ピクセルにおける複素平面上の座標 (re, im) を計算します。その各点において mandelbrot_calc を呼び出し、与えられた最大反復回数に達するまでに漸化式が発散するかどうか判定してピクセルの色を計算しています。

do y = 1, image_h
  im = lerp(im_min, im_max, real(image_h - y + 1) / real(image_h))
  do x = 1, image_w
    re = lerp(re_min, re_max, real(x) / real(image_w))
    call mandelbrot_calc(re, im, niter, nescape)
    if (nescape <= niter) then
      call calc_pixel_value(re, im, nescape, niter, r, g, b)
      pixels(PIXEL_INDEX_RED, x, y) = r
      pixels(PIXEL_INDEX_GREEN, x, y) = g
      pixels(PIXEL_INDEX_BLUE, x, y) = b
    endif
  enddo
enddo

リスト1. ピクセル値計算処理

以下では、インテル® Fortran Studio XE で提供されている各ツールを使用しながら、このプログラムを改善していきます。

3. ベクトル化によるプログラムの高速化

自動ベクトル化の利用

インテル® Fortran コンパイラーでは、コンパイラースイッチ /O2 または /O3 (Linux の場合 –O2 または -O3) による高レベルの最適化(HLO) の他、プロシージャー間の最適化(IPO)、強力な自動ベクトル化や自動並列化といった最適化機能が用意されています。

実行コードを最適化したい場合、まずこれらの最適化オプションを指定してプログラムのビルドを行います。今回は、プログラム実行環境においてインテル® AVX 命令セットが使用可能であることを前提に、/QxAVX コンパイラースイッチによってインテル® AVX を使用する実行コードを生成するよう指示します。

/QxAVX の代わりに、例えば /QaxAVX のように /Qax オプションを利用することで、インテル® AVX などの特定の SIMD 命令がサポートされていない環境でも実行可能なプログラムを作成することができます。Windows 環境ではスタートメニューに登録される Command Prompt メニュー以下のショートカットから、Linux 環境では compiler.sh スクリプトを呼び出してコマンドラインからのビルド環境を作成し、以下のコマンドでプログラムをビルドします。

Windows の場合

> ifort /O2 /Qip /QxAVX /MD mandelbrot.f90

Linux の場合

$ ifort –O2 –ip –xAVX –shared-intel mandelbrot.f90

プログラムのソースファイルは一つだけなので、IPO のオプションとして複数のファイルのプロシージャー間で最適化を行う /Qipo オプションの代わりに、単一のファイルを対象とする /Qip を指定しています。

プログラムの実行時間を計測したいのであれば、インテル® Fortran コンパイラーにより提供されているポータブル関数 dclock を利用することができます。リスト1. のピクセル値計算処理の前後で何度か実行時間を測定したところ、検証に使用したWindows マシンでは、図1. のマンデルブロー集合を得るために 6.78 秒程度の平均実行時間を要しました。

インテル® VTune™ Amplifier XE を使用したイベントベースの hotspot 分析

インテル® VTune™ Amplifier XE を使用してこのプログラムに最適化できる部分がないか調べてみることにします。まず、インテル® VTune™ Amplifier XE で分析を行うために、ビルド・オプションを少し変更します。サブルーチンや関数などと関連付けられた詳細なパフォーマンス・データを取得するために、/debug オプションを指定してデバッグ情報を生成します。

また、最適化によってインライン化されるサブルーチンや関数の関連も確認したい場合は、/debug オプションの引数に inline-debug-info を指定してインラインコードに対する拡張デバッグ情報を生成します(注3.)。最適化オプションは指定したままであることに注目してください。リリース版のバイナリーに対応する、行ライン情報があればソースに対応した解析ができます。

Windows の場合

> ifort /O2 /Qip /QxAVX /debug:inline-debug-info /MD mandelbrot.f90

Linux の場合

$ ifort –O2 –ip –xAVX –debug inline-debug-info –shared-intel mandelbrot.f90

図 2. は、ボトムアップ・ペーンに表示された Lightweight Hotspots 分析の結果です。この分析では CPU のパフォーマンス・モニタリング・ユニット (PMU) を使用したイベントベース・サンプリングが行われ、分析結果のグループ(Grouping) の設定を変更することで、関数などのレベルで個々のパフォーマンス・メトリックを確認することができます。

この簡単なプログラムでは、パフォーマンスを改善するためには mandelbrot_calc のチューニングを行う必要があること、また、その CPI 値(1命令あたりのサイクル数) は現時点でも 0.996 と悪くはない値であることが分かります。

図 2. Lightweight Hotspots 分析結果

最もCPU時間を消費している mandelbrot_calc の行をダブルクリックすると、ソース・ウィンドウ(図 3.)が表示されます。

ソース・ウィンドウでは実行に要したCPU時間をソースコードの行単位で確認することが出来ます。mandelbrot_calc では do while 文の条件チェック (i <= niter .and. (x * x + y * y) <= 4.0) において 5.744 秒と、ほとんどの時間がこの部分の実行に費やされています。

この行に対応するアセンブリ・コード(右側の水色の背景色で表示されている部分) から、スカラー演算が行われていることがわかります。

図 3. ソース・ウィンドウ

ベクトル化のためのソースコードの修正

なぜ mandelbrot_calc の do while 文の条件チェックの部分がベクトル化されなかったのか、インテル® Fortran コンパイラーで提供されている最適化レポートを利用して、ベクトライザーの診断情報を確認してみます。

Windows の場合

> ifort /O2 /Qip /QxAVX /Qvec-report:2 /MD mandelbrot.f90

Linux の場合

$ ifort –O2 –ip –xAVX –vec-report:2 –shared-intel mandelbrot.f90

mandelbrot.f90(76): (col. 9) remark: ループはベクトル化されませんでした: 非標準のループはベクトル化候補ではありません.

ループで実行される計算がベクトル化されるためには、実行時にループの反復回数が分からなければいけないという条件がありますが、今回のループの反復回数はループ処理内部で行われる計算に依存しています。このような場合に上記の診断メッセージが表示されます。

このループの処理自体をベクトル化可能なコードに変更するのは困難ですが、画像上の各ピクセルに対して同じ計算を行っているので、いくつかのピクセルに対する計算を一つのループで実行するようにコードを記述し直すことで、ベクトル化が容易になるはずです。

リスト2. がコンパイラーによるベクトル化のために修正を行ったコードの一部になります。変数 re, im はともに1から NPOINTS までのインデックスを持つ配列に変更し、値をいくつか変えて行った分析の結果を参考に、NPOINTS は値16 の定数として定義しています。この処理では、画像の水平方向 NPOINTS 分のピクセルに対応する複素平面上の座標を一度に mandelbrot_calc に渡しています。mandelbrot_calc も配列表記を使用して複数の座標に対する計算を一度に行うよう変更しています。

do y = 1, image_h
  im = lerp(im_min, im_max, real(image_h - y + 1) / real(image_h))
  do x = 1, image_w, NPOINTS
    re = (/ (lerp(re_min, re_max, real(i) / real(image_w)), i = x, x + NPOINTS - 1) /)
    call mandelbrot_calc(re, im, niter, nescape)
    do i = x, min(x + NPOINTS - 1, image_w)
      if (nescape(i - x + 1) <= niter) then
        call calc_pixel_value(re(i - x + 1), im(i - x + 1), nescape(i - x + 1), niter, r, g, b)
        pixels(PIXEL_INDEX_RED, i, y) = r
        pixels(PIXEL_INDEX_GREEN, i, y) = g
        pixels(PIXEL_INDEX_BLUE, i, y) = b
      endif
    enddo
  enddo
enddo

リスト2. ピクセル値計算処理(ベクトル化のための変更)

このコードを再コンパイルし、インテル® VTune™ Amplifier XE による Lightweight Hotspots 分析を行ったところ、図4. の結果が得られました。最もCPU時間が費やされている行でもその値は 294 ミリ秒となり、サブルーチン mandelbrot_calc で費やされたCPU時間は 1.277秒、またそのCPI値は 0.416 へと改善されました。

図4. ベクトル化された mandelbrot_calc のソース・ウィンドウ

/Qvec-report:2 によるベクトライザーの診断情報でも以下のようなメッセージが表示され、ベクトル化によりパフォーマンスが改善されていることが確認できます。

mandelbrot_vec.f90(76): (col. 9) remark: ループがベクトル化されました.

4. OpenMP によるプログラムの並列化

インテル® Visual Fortran XE 2011 では、並列化の手段として従来の共有メモリー環境で利用可能な OpenMP に加え、分散メモリー環境へのスケールも可能な Co-Array もサポートされるようになりましたが(注4.)、今回は導入が比較的容易に行える OpenMP を利用してプログラムの並列化を行います。

画像のピクセル値の計算において、垂直方向の各ピクセルの計算もそれぞれ独立しているので、並列化の良い候補と言えます。リスト3. では、PARALLEL 指示構文を使用して ピクセル値計算処理における最外ループを並列化しています。

!$OMP PARALLEL DO PRIVATE(i, x, y, re, im, nescape, r, g, b)
do y = 1, image_h
  ! リスト2. と同じコード
enddo
!$OMP END PARALLEL

リスト3. ピクセル値計算処理(OpenMP PARALLEL 指示構文の追加)

OpenMP を有効にしてプログラムをビルドするには、/Qopenmp オプションを指定する必要があります。以下の例では /Qopenmp-report オプションも指定して、OpenMP による並列化が実際に行われていることを確認しています。

Windows の場合

> ifort /O2 /Qip /QxAVX /Qopenmp /Qopenmp-report:2 /MD mandelbrot_vec.f90

Linux の場合

$ ifort –O2 –ip –xAVX –openmp –openmp-report:2 –shared-intel mandelbrot_vec.f90

mandelbrot_vec.f90(127): (col. 7) remark: OpenMP 定義ループが並列化されました.

インテル® Inspector XE によるスレッディング・エラーのチェック

インテル® Inspector XE は OpenMP を用いて記述されたプログラムの問題分析にも役立ちます。インテル® Inspector XE の分析では、より正確な結果を得るためにプログラムの最適化を無効(/Od オプション)にして分析を行うことが推奨されています。スレッディング・エラーの確認のために、以下のコマンドラインを使用してプログラムの再ビルドを行いました。

Windows の場合

> ifort /Od /debug:full /Qopenmp /MDd mandelbrot_vec.f90

Linux の場合

$ ifort –O0 –debug full –openmp –shared-intel mandelbrot_vec.f90

また、インテル® Inspector XE による動的分析ではプログラム実行中にメモリー・エラーあるいはスレッディング・エラーが観測されればよいので、最大反復回数を 1000 に、出力画像のサイズを 800×600 にそれぞれ縮小し、プログラム実行時の計算量を減らすことで分析に要する時間を減らしています。

インテル® Inspector XE のスレッディング・エラー分析の設定の一つであるデッドロックおよびデータ競合の特定(Locate Deadlocks and Data Races)を利用して分析を行った結果が図5. になります。画面上部の問題(Problems)のリストには観測された問題の種類が、下部のコード位置(Code Locations)には選択中の問題に関連しているソースコードの箇所がコード片とともに表示されています。各々のコード位置をダブルクリックすることによって、該当するソースコードの表示や、さらに外部エディタで編集することもできます。

図5. スレッディング・エラー分析結果 (Locate Deadlocks and Data Races 分析)

図5. の分析では、並列化されたプログラムでしばしば発生するデータ競合問題が報告されています。PARALLEL 指示構文によって並列化されたブロックの処理を行う各スレッドが、ピクセル値を保持する同一の変数 r, g, b の値を参照あるいは変更していたために、この問題は画像中の細かなノイズとして結果に影響していました。PRIVATE 指示節に変数 r, g, b も追加して、これらの変数をスレッドごとにローカルな変数(プライベート変数)とすることで、問題を回避できます。

インテル® VTune™ Amplifier XE による並行性の確認

インテル® VTune™ Amplifier XE には、並列化に注目してパフォーマンスを調査する分析タイプも用意されています。OpenMP を有効にしてインテル® VTune™ Amplifier XE での分析向けの詳細情報を含むビルドを行ったプログラムに対して並行性分析(Concurrency 分析)を行い、図6. ではその結果がトップダウン・ツリーとして表示されています。

Windows の場合

> ifort /O2 /Qip /QxAVX /Qopenmp /Qopenmp-report:2 /debug: inline-debug-info /MD mandelbrot_vec.f90

Linux の場合

$ ifort –O2 –ip –xAVX –openmp –openmp-report:2 –debug inline-debug-info –shared-intel mandelbrot_vec.f90

関数あるいはサブルーチンの呼び出し関係を明らかにする上部のトップダウン・ツリーに加え、下部に表示されているタイムライン・ペインではプログラム実行中の各スレッドの状態が示されています。

検証に使用した環境では8つの論理コア(物理コア数 4、Hyper Threading テクノロジー有効)が利用できるので、メインスレッドとOpenMP のワーカースレッドあわせて8つのスレッドで計算が行われていますが、それらのスレッドのうちのいくつかでは OpenMP 並列化領域の実行中にすぐに計算が終了してしまい、並列化領域の実行が終了するまで待機状態(Waits)となっています。

マンデルブローの描画処理では領域によって mandelbrot_calc で実行されるループの回数に偏りがあるため、スレッドごとにブロック単位で分割されるデフォルトの STATIC スケジュールでは各スレッドの計算量に差が生じます。このような場合は PARALLEL 指示構文に SCHEDULE 節を使用してスケジュール方法を変更します。

SCHEDULE(dynamic) を追加指定して再度分析を行ったところ、図7.のような結果が得られました。この結果では待機状態になっている部分はほぼなくなりました。タイムライン・ペインの下部に表示されているスレッド並行性 (Thread Concurrency) も、SCHEDULE 節を指定していない場合は時間がたつにつれて並行性が 8 から次第に低下していますが、SCHEDULE(dynamic) を指定した場合は OpenMP 並列化領域の実行中ほぼ 8 を保っています(最下部に表示されている緑色のグラフ)。

他の最適化オプションなども試してみることで、まだ若干のパフォーマンス向上が見込めるかもしれませんが、OpenMP による並列化もうまくいったようなので今回はここまでとします。

図 6. 並行性分析 (SCHEDULE 句指定なし)

図 7. 並行性分析 (SCHEDULE 句で dynamic を指定)

5. 終わりに

インテル® Fortran Studio XE 2011 に同梱されているそれぞれの開発ツールでは多彩な機能が用意されていますが、本記事ではプログラムの高速化のための作業を通してそれらの一部をみてきました。今回は紹介できませんでしたが、例えばインテル® Fortran Composer XE では、ガイド付き自動並列化(GAP)やプロファイルに基づく最適化(PGO)といった機能も提供されており、これらもプログラムのチューニングの際に役立つはずです。

実行パフォーマンスのボトルネックとなっている箇所を特定するためにはインテル® VTune™ Amplifier XE の Lightweight Hotspots 分析を利用しましたが、ユーザーモード・サンプリング/トレーシングを利用する Hotspot 分析を使用すれば、hotspot となっている箇所をコール・ツリー表示で特定することもできます。

インテル® Inspector XE ではスレッディング・エラー以外にメモリーに関連する問題も分析でき、また、インテル® Fortran Studio XE 2011 などの Studio XE 製品で利用可能なソースコードの静的分析機能もあわせて使用することで、高品質なプログラムの開発を行えます。製品にはいくつかのサンプル・プログラムも添付されているので、それらも利用して各製品で用意されている機能を試してみてください。

注1.     Microsoft Visual Studio 製品版がインストールされていない環境でも、代わりに製品に同梱されている Microsoft Visual Studio Shell を使用してプログラムの開発を行うことができます。同梱されている Visual Studio Shell では開発用言語として Fortran しか使用できないといった制限があります。サポートされている Microsoft* Visual Studio* のバージョンに関しては、製品リリース・ノート等で確認してください。

注2.     インテル® Fortran Studio XE 2011 にはインテル® MPI ライブラリーは同梱されていません。

注3.     Windows 環境において検証に使用したバージョン(インテル® Visual Fortran Composer XE 2011 Update 9、インテル® VTune™ Amplifier XE 2011 Update 7)では、インテル® VTune™ Amplifier XE の分析結果においてインライン化された関数あるいはルーチンの名称が表示されない問題があったので、記事中の分析では /inline:none も指定してインライン化を抑制しました。この点は将来リリースされるバージョンで改善される予定です。

注4.     インテル® (Visual) Fortran Composer XE 2011では、共有メモリー環境で実行させることができる Co-Array プログラムを開発できます。Co-Array を分散メモリー環境でも使用するためには、インテル® Cluster Studio 製品のライセンスが必要になります

著者紹介

Takahiro Baba。オープン系、およびパッケージソフトウェア開発者としての経験を経て、現在は米国 XLsoft Corporation に勤務。インテル® ソフトウェア開発製品に関するテクニカル・サポートや製品のテスティングを担当。

タイトルとURLをコピーしました