gccのprofiled optimization
CPUをアーキテクチャ指定による最適化の具合を調べていたんだけど、最近のgcc*1にはprofiled optimizationの機能が入っていることに気付いたのでこちらを測定してみた。
測定に使ったベンチマークはSciMark2.0のANSI C版。コンパイラはDebian sid/amd64のgcc version 4.2.1 20070516 (prerelease) (Debian 4.2-20070516-1)。
通常にコンパイルしたバイナリ
** ** ** SciMark2 Numeric Benchmark, see http://math.nist.gov/scimark ** ** for details. (Results can be submitted to pozo@nist.gov) ** ** ** Using 2.00 seconds min time per kenel. Composite Score: 501.16 FFT Mflops: 324.21 (N=1024) SOR Mflops: 426.32 (100 x 100) MonteCarlo: Mflops: 176.60 Sparse matmult Mflops: 777.88 (N=1000, nz=5000) LU Mflops: 800.78 (M=100, N=100)
実行プロファイルを喰わせて作ったバイナリ
** ** ** SciMark2 Numeric Benchmark, see http://math.nist.gov/scimark ** ** for details. (Results can be submitted to pozo@nist.gov) ** ** ** Using 2.00 seconds min time per kenel. Composite Score: 633.20 FFT Mflops: 436.06 (N=1024) SOR Mflops: 454.47 (100 x 100) MonteCarlo: Mflops: 177.19 Sparse matmult Mflops: 926.30 (N=1000, nz=5000) LU Mflops: 1171.96 (M=100, N=100)
相手が内容固定の数値計算というプロファイル最適化が最も効きそうな素材ではあるが、25%ぐらい速くなった。こりゃ凄い。
実行プロファイルの作り方は、まず実行プロファイルを出力する専用バイナリを作ることから始まる。これは、コンパイル時とリンク時に"-fprofile-generate"というオプションを付けてmakeすると出来る。このバイナリを実行すると.o毎に.gcdaや.gcnoという実行時の統計情報を書き出してくれる。あとは再度"-fprofile-use"というオプションに変えてmakeし直すと先に出力された統計情報に基いて最適化を行ったバイナリが作成される。
今回はどれぐらい効果があるか調べたいだけだったので、単純なシェルスクリプトを書いてバイナリを作った。
#!/bin/sh make clean rm *.gcda rm *.gcno make CFLAGS="-O3 -fprofile-generate" LDFLAGS="-fprofile-generate" CC=gcc-4.2 ./scimark2 ./scimark2 make clean make CFLAGS="-O3 -fprofile-use" LDFLAGS="-fprofile-arcs" CC=gcc-4.2
ちなみに、SciMarkにはキャッシュに入り切る通常のサイズの計算と主記憶にアクセスする必要がある程度の大きさの2通りの測定が出来る。上記の結果は小さい方の結果で、ほぼプロセッサのキャッシュで演算内容が完結するぐらい。この小さい方の演算のみで最適化を行ったバイナリに対してデータフローが異なるであろうラージモードの演算をやらせると、通常の最適化の結果と1%程度速いところまで差が縮まった。遅くなるようなことはないようだが、演算内容に特化した最適化が行われるのは確かなようだ。
詳しい動作原理などはRadium SoftwareのProfile feedback in GCCという記事に詳しい。同じくmanにもある通り、値の内容に応じた最適化(演算結果を変えない範囲でシフト演算などより高速な処理にコンパイルする)やループの展開(事前に繰り返し回数を決定できるようなループを単純な列挙に展開する)、分岐の結果頻度に応じた最適化などを行う。
実行時プロファイルによる最適化は最近のJVMではわりと使われる手法のようで、JDK1.5ぐらいから入っていたように記憶している。起動時の遅さはともかくとして、実行時間が十分に長いアプリケーションでは「C言語で記述したものよりも速い」ということが起こり得る。実際、SciMark2.0のページにはJavaでの実行結果とそのプラットフォームがランキングで紹介されているが、上記結果を取ったPCとほぼ同等のCPU性能のマシンだと若干良い結果を出しているようである。
Visual C++のコンパイラなどではかなり以前から備わっていた機能ではあるが、せっかく最近のgccを使うならばこういった機能を使ってみるのも良いかもしれない。