コンパイラを変えるだけでパフォーマンス向上、インテル コンパイラーの実力を見る

 「よりパフォーマンスの高いプログラムを作成するにはアセンブラを駆使すべし」という話を聞いたことがある人も多いだろう。これは、C/C++言語で記述されたプログラムには冗長な部分があるため、ノウハウを持つプログラマがアセンブラでチューニングしたプログラムの方が高いパフォーマンスを得られる、ということであった。しかし、現在では必ずしもこのことは当てはまらなくなっている。その理由は、コンパイラの進化と、CPUおよびPCアーキテクチャの複雑化にある。

 最近のコンパイラのほとんどは最適化機能と呼ばれる、ソースコードをより効率の良い形に自動変換する機能を備えている。基本的な最適化の例としては、プログラム内で実際には使われていない処理の省略や、冗長なforループの自動展開などが挙げられるが、最近ではこのほかにも高速に処理を行えるようプログラムの実行順序を入れ替えたり、頻繁に呼び出される関数を自動的にインライン展開する、といった高度な最適化機能を持つコンパイラも増えている。

 また、CPUやPCアーキテクチャが複雑になるにつれ「どのようなコードがより効率的に実行できるか」ということを予測しにくくなっているというのも、コンパイラの最適化が有用な理由の1つだ。最近のCPUは命令コードを別のマイクロコードに変換して実行するため、「最適のように見える」コードが、実はCPU的には全然最適ではない、という結果も往々にしてある。さらにメモリキャッシュを有効利用しているかどうかでもパフォーマンスは大きく変化するほか、複数スレッドを同時実行できる環境ではスレッドの有効活用なども考えなければならない。

 広く使われているC/C++コンパイラとして、Windows環境ならマイクロソフトのVisual C++、LinuxやMac OS X環境であればGCCが有名だ。Visual C++やGCCは入手しやすいこともあり、多くのユーザーがこれらのコンパイラを使っていることだろう。これらのコンパイラにも最適化機能は備わっているが、最新のマルチコアCPU向けの最適化・並列化を行ってパフォーマンスを追求したいなら、インテルが開発し、インテル製CPUに向けた高い最適化を謳う「インテル コンパイラー」という製品がある。

 インテル コンパイラーは、CPUメーカーが開発しているということで、最新のCPUへ向けた最適化やそれらが備えるSSE 4.1といった命令セットへの対応、そして自動並列化機能など、パフォーマンスの高いバイナリコードを生成する機能を備えている。パフォーマンスが必要とされる分野での採用例も多く、たとえばOracleは同社のデータベース製品のコンパイルにインテル コンパイラーを採用している。

 しかし、コンパイラを変えることで本当にパフォーマンスが変わるのか、またその違いはどの程度なのか気になる方も多いだろう。そこで以下では工学分野などで使われる数値演算プログラムを例に、生成したコードのパフォーマンスを検証してみたい。

インテル コンパイラーの製品構成

 パフォーマンス比較を行う前に、まずはインテル コンパイラーの製品ラインアップを簡単に紹介しておこう。インテル コンパイラーには、C/C++コンパイラ製品「インテル C++ コンパイラー プロフェッショナル・エディション」とFortranコンパイラ製品「インテル (Visual)Fortran コンパイラー プロフェッショナル・エディション」の2製品が用意されている。対応プラットフォームは32ビットおよび64ビット(x86およびx64、IA-64)のWindowsおよびLinux、Mac OS Xだ。

 なお、以前はコンパイラ単体での販売もされていたが、最新版のインテル コンパイラー 11.0からは、「インテル スレッディング・ビルディング・ブロック」(Threading Building Block、以下TBB)と呼ばれる並列化ライブラリや「インテル マス・カーネル・ライブラリー」(Math Kernel Library、以下MKL)という数値計算ライブラリ、動画/画像/音声/信号処理用ライブラリ「インテル インテグレーテッド・パフォーマンス・プリミティブ」(Integrated Performance Primitive、以下IPP)が付属する「プロフェッショナル・エディション」のみにラインアップがまとめられている。これらのライブラリもコンパイラと同様にインテルによって開発されており、インテルCPU向けの最適化や並列化などが行われているのが特徴だ。

 なお、日本国内においては、これらの製品はエクセルソフトが代理店となり、日本語版の販売とサポートを行っている。日本語版はマニュアルやインストーラーなどが日本語化されており、また技術サポートサービスなども提供されている。エクセルソフトのWebサイトでは体験版も入手できるので、興味を持たれた方はダウンロードして試してみると良いだろう。

インテル コンパイラーの特徴

 インテル コンパイラーの特徴は、高速に実行できるバイナリコードをユーザーが意識せずに作成できる、という点にある。さらに、インテル C++ コンパイラーのWindows版はVisual C++、Linux/Mac OS X版はGCCと機能やコンパイルオプションの両方で互換性があり、Visual C++やGCCでコンパイルできるプログラムならそのままインテル コンパイラーでもコンパイルできる。

 また、Windows版インテル C++ コンパイラーにはVisual C++用のプラグインが付属しており、Visual C++のIDEであるVisual Studioを使っている場合、簡単にインテル C++ コンパイラーを利用できる。たとえばVisual Studio 2008の場合、コンパイルしたいプロジェクトを開き、ツールバーの「インテル C++を使用」ボタンをクリックするだけで良い(図1)。これだけでプロジェクトの設定が自動的に変更され、インテル C++ コンパイラーでコンパイルが行われるようになる。

図1 Visual Studioに追加されるツールバー(赤で囲んだ部分)
図1 Visual Studioに追加されるツールバー(赤で囲んだ部分)

 プラグインを導入することで、インテル C++ コンパイラーの最適化オプションをGUIで設定することも可能だ。たとえばVisual Studio 2008では、図2のようにプロジェクトの設定でコンパイラオプションを指定できるが、プラグインを導入するとこの画面に図3のような「インテル固有」項目が追加され、インテル C++ コンパイラーのコンパイルオプションも設定できるようになる。複雑な最適化オプションを覚えずにGUIで最適化設定が行えるため、非常に使いやすい。

図2 Visual Studio標準の最適化オプション画面
図2 Visual Studio標準の最適化オプション画面


図3 追加されるインテル C++ コンパイラーの最適化設定画面
図3 追加されるインテル C++ コンパイラーの最適化設定画面

 なお、Windows版のインテル C++ コンパイラーはVisual C++に含まれるリンカーを使用するため、別途Visual C++ 2003以降が必要となる。無償版であるVisual C++ 2005/2008 Express Editionとの組み合わせでも利用は可能だが、Express Editionの場合Visual Studioとの統合機能は利用できず、またVisual Studioからのコンパイルも行えないので注意してほしい(コマンドラインでのコンパイルは可能)。Visual Studio 2008を持っていないがとりあえず試したい、という場合は90日間評価版を利用しよう。

インテル C++ コンパイラーを使ったプログラムのパフォーマンスを検証する

 CPUメーカーであるインテルが開発しているといえども、本当にコンパイラを変えるだけで目に見えるほど実行速度が変わるのかは気になるところだ。そこで、まずは簡単な数値計算プログラムを用意し、実際にインテル C++ コンパイラーでコンパイルを行う手順を説明するとともに、ほかのコンパイラを使ってコンパイルしたものと比べてどの程度パフォーマンスが変わるのか検証してみよう。なお、下記の検証では、Visual Studio 2008を使用して行っている。

 初めに用意したのは、簡単な浮動小数点演算を行うプログラムだ。このプログラムは、要素としてdoubleの値を持つn×nサイズの行列同士の乗算をm回繰り返す、というもので、行列演算部分はリスト1のようになっている。

リスト1 サンプルプログラム内の行列乗算関数

/* A = B * C; matrixA = matrixB * matrixC */
void prod_matrix( double* matrixA, double* matrixB, double* matrixC, int dim ) {
  int i, j, m;

  memset( matrixA, 0, sizeof(double)*dim*dim );

  for( i = 0; i < dim; i++ ) {
    for( m = 0; m < dim; m++ ) {
      for( j = 0; j < dim; j++ ) {
        AT(matrixA,i,j) += AT(matrixB,i,m) * AT(matrixC, m, j);
      }
    }
  }
}
※ソースコード全文はこちら

○インテル C++ コンパイラーのインストール

 インテル C++ コンパイラーはインストールの際、コンパイラ本体およびVisual Studioプラグイン、MKLやIPP、TBBといった付属ライブラリについて、それぞれインストールの可否を選択できる(図4)。なお、Visual Studioプラグインは対応するVisual Studioがインストールされていないと選択できない。また、ここでインストールしなかったコンポーネントも、必要になった時点で再度このインストーラを起動すれば後から追加インストールできる。

図4 インテル C++ コンパイラーのインストーラ
図4 インテル C++ コンパイラーのインストーラ


○Visual Studioプロジェクトの作成

 インテル C++ コンパイラーはコマンドラインからも利用できるが、今回はVisual Studioでプロジェクトを作成し、そこからインテル C++ コンパイラーを呼び出して利用する。Visual Studioからインテル C++ コンパイラーを利用する場合、まずVisual Studioでプロジェクトを作成し、続いてインテル C++ コンパイラーを利用するように設定すればよい。

 今回は新たにプロジェクトを作成するので、まずVisual Studioを起動し、「ファイル」メニューから「新規作成」-「プロジェクト」を選択して「新しいプロジェクト」画面を開き、プロジェクト名や保存先、ソリューション名などを指定してプロジェクトを作成する。テンプレートとしては「Win32 コンソールアプリケーション」を選択した(図5)。

図5 Visual Studioでのプロジェクト作成
図5 Visual Studioでのプロジェクト作成


○ファイルの追加とコンパイル設定

 続いて、作成したプロジェクトにソースコードを追加し、コンパイラの指定とコンパイル設定を行う。コンパイルしたいプロジェクトをVisual Studioのソリューション エクスプローラで選択し、追加された「インテル C++」ツールバー中にある「インテル C++を使用」ボタンをクリックすることで、インテル C++ コンパイラーを利用するよう各種設定が自動的に変更される。

 また、これによって「プロジェクト」メニューの「プロパティ」で表示できるプロパティ画面でインテル C++ コンパイラーの挙動も設定できるようになる。たとえば「C/C++」の「最適化」項目では、使用するインテル C++ コンパイラーの最適化オプションを指定できる。

 なお、インテル C++ コンパイラーを指定した場合でも、「Visual C++を使用」ボタンをクリックすればいつでもVisual C++でコンパイルするように設定を戻すことができる。

○コンパイルの実行

 コンパイル/ビルドについては、通常通り「ビルド」メニューやツールバーの「ソリューションのビルド」から行える。ビルドの出力では、インテル C++ コンパイラーを利用した処理については「インテル C++ 環境」と表示され、Visual C++のコマンドを呼び出す処理については「Microsoft VC++ 環境」と表示される(図6)。

図6 Visual Studioの出力ウィンドウ
図6 Visual Studioの出力ウィンドウ


 なお、Linux環境でインテル C++ コンパイラーを利用する場合は、単純にコンパイラをgccからicl(インテル C++ コンパイラーのコマンドライン)に変更するだけで良い。Linux版インテル C++ コンパイラーはGCCと互換性があるため、オプション等を変更する必要はない。

作成したプログラムのパフォーマンスを比較する

 それでは、以上のようにして作成したプログラムを、Visual C++やGCCでコンパイルしたプログラムと比較してみよう。プログラムのパラメータはn=400およびm=100、つまり「400×400サイズの行列を100回乗算する」に設定した。また、実行時間の計測にはclock()関数を使用し、プログラムを開始した直後から終了する直前までの経過時間を計測している。

 テストプログラムのコンパイルおよび実行に使用したのは、CPUとしてCore 2 Duoを搭載したWindows Vistaマシンだ(表1)。コンパイラとしてはインテル C++ コンパイラーおよびVisual Studio 2008付属のC++コンパイラ、CygwinでインストールしたGCC 3およびGCC 4を用意し、それぞれでコンパイルしたプログラムを比較する。また、インテル C++ コンパイラーについては並列化の効果を確認するため、自動並列化を有効にしたものと、無効にしたものを用意した。それぞれのコンパイルオプションは図7~9およびリスト2のように設定している。

 なお、インテル C++ コンパイラーで並列化オプションを有効にした場合、デフォルトでは確実に高速化できる個所のみ自動並列化を行うが、「/Qpar-theshold」オプションを併用することで、自動並列化を行う閾値を変更できる。今回は図9のように閾値を「0」に設定し、並列化が行える個所はすべて並列化するように指定した。

表1 テストプログラムのコンパイルおよび実行に使用したPCのスペック
項目 スペック
OS Windows Vista Business SP1
CPU Core 2 Duo E6550(2.33GHz)
メモリ 2048MB


図7 インテル C++ コンパイラの最適化オプション設定(並列化なし)
図7 インテル C++ コンパイラの最適化オプション設定(並列化なし)


図8 インテル C++ コンパイラの最適化オプション設定(並列化あり)
図8 インテル C++ コンパイラの最適化オプション設定(並列化あり)


図9 インテル C++ コンパイラの追加オプション設定画面
図9 インテル C++ コンパイラの追加オプション設定画面


図10 Visual C++の最適化オプション設定
図10 Visual C++の最適化オプション設定


リスト2 GCC 3およびGCC 4のコンパイル

↓GCC 3で最適化レベル2、Pentium 4系CPUに向けた最適化を指定してコンパイル
> gcc.exe -O2 -march=nocona -o matrix1_gcc3_O2.exe matrix1.c
 :
↓GCC 3で最適化レベル3、Pentium 4系CPUに向けた最適化を指定してコンパイル
> gcc.exe -O3 -march=nocona -o matrix1_gcc3_03.exe matrix1.c
 :
↓GCC 4で最適化レベル2、Core 2系CPUに向けた最適化を指定してコンパイル
> gcc-4.exe -O2 -march=core2 -o matrix1_gcc4_02.exe matrix1.c
 :
↓GCC 4で最適化レベル3、Core 2系CPUに向けた最適化を指定してコンパイル
> gcc-4.exe -O3 -march=core2 -o matrix1_gcc4_O3.exe matrix1.c
 :


テストプログラムの実行結果

 テストプログラムの実行結果をまとめたものが、次の表2である。インテル C++ コンパイラーでコンパイルしたプログラムをVisual C++でコンパイルしたものと比べると、まず並列化を使用しない場合でも、プログラムの実行にかかった時間が半分以下にまで短縮できていることが分かる。さらに並列化を有効にした場合、実行時間は4分の1程度にまで短縮できている。

表2 テストプログラム実行結果
コンパイラ 1回目 2回目 3回目 平均
最適化無し(Visual C++) 66.290秒 65.815秒 65.955秒 66.020秒
Visual C++ 6.650秒 6.660秒 6.685秒 6.665秒
GCC 3(-O2) 20.685秒 20.686秒 20.686秒 20.686秒
GCC 3(-O3) 9.844秒 9.843秒 9.828秒 9.838秒
GCC 4(-O2) 9.438秒 9.438秒 9.421秒 9.432秒
GCC 4(-O3) 20.358秒 20.341秒 20.342秒 20.347秒
インテル C++ コンパイラー 3.120秒 3.165秒 3.110秒 3.132秒
インテル C++ コンパイラー(並列化) 1.730秒 1.775秒 1.745秒 1.75秒


コンパイラを変えるだけで高速化が実現

 このテスト結果が示すとおり、インテル C++ コンパイラーはほかのコンパイラと比較し、より高速で動作するバイナリコードを生成できる。Visual C++との互換性も非常に高く、インテル C++ コンパイラーを導入し、IDEで簡単な設定変更を行うだけで手軽に高速化が行えた。

 ただし、ソースコードによっては、インテル C++ コンパイラーをそのまま使うだけでは劇的な高速化が期待できない場合もある。そこで加えて活用したいのが、インテル コンパイラーに付属するMKLやIPPといった高速ライブラリである。真にインテル コンパイラーのパフォーマンスを享受したいのであれば、このようなライブラリの活用も検討すべきだろう。

 また、いくつか注意すべき点もある。まず、インテル コンパイラーは高度な最適化を行うため、より多くのメモリやコンパイル時間を必要とするようだ。通常気にする必要はないとは思うが、大規模なアプリケーションをコンパイルする場合、最適化の設定によっては非常に多くのメモリが必要となる。そのため、開発に使用するPCはある程度高いスペックのものを用意すべきだろう。