ゴミ箱の中のメモ帳

まだ見ぬ息子たちへ綴る手記

gccとclangとicc

とある会話の中で、昔のプログラミング事情の話になった。

昔昔のその昔、世間のコンパイラが軒並み有料であった頃、コンパイラの価格によってプログラミング言語の選択が制限される時代があった。例えばC言語にしても、Windowsであれば「Borland C++ Compiler」や「LSI C-86試食版」、「Microsoft Visual C++」、「Intel C Compiler」等、各ベンダーがバラバラにコンパイラを制作していたためにベンダーの独自拡張の雨あられコンパイラ別に流派も別れた。

当時はC言語を勉強するにしてもそれらの中から「どのコンパイラを選ぶか」によって将来が左右された。私のようなアホでもない限り、いきなり30万円も出してMicrosoft Visual Studioを買う人間は居なかった。

さらに、コンパイラが高すぎるという理由で「Delphi」なんかを始めるプログラマや、Delphiこそ至高と考え「Kylix」に手を出して血涙を流したプログラマも居た。まさに当時はコンパイラ戦国時代。


そして現在はGCCの台頭から、各フリー(自由)コンパイラの充実、更にはそれらの普及からベンダー生コンパイラの消滅と、ベンダー生コンパイラのGCC互換と、時代の潮流は変化し、「コンパイラ販売」から、「開発環境販売」や「ライブラリ販売」にシフトしている。

きつねさんでもわかるLLVM ~コンパイラを自作するためのガイドブック~

きつねさんでもわかるLLVM ~コンパイラを自作するためのガイドブック~


そんな当時はコンパイラの性能からサポート機能、拡張機能、対応C言語のバージョン、実行速度やバイナリサイズなどピンキリであった。それ故に、C言語を学ぶものはコンパイラについても学ぶ必要があり、それぞれのコードに応じてコンパイラを使い分けたりもした。

だが現在C言語を学んでいる方々はこの時代の事を知らないようで(知らなくてもいいし)、コンパイラによる性能差がどれほどのものか理解されていないようだ。例えば当時ではアセンブリを読んで最適化について調べたりもしたようだが、現在はとにかく「-O2」をつければいいと考えられている。

まぁ、懐古主義でもないし知る必要のないことは知らなくてもいいと思うのだが、それぞれを「コンパイラ」としか認識していなければ特定のコードがボトルネックになっている際に、コンパイルオプションやコンパイラを変更するという選択肢が無いことになってしまう。


例えば現在多く利用されているC言語コンパイラにはgccとclangがある。それに加えてベンダーの最高権力はCPUメーカが制作している「Intel C++ Compiler」だろう。最低でもこの3つについては簡単にでも知っておく必要はある。

例えば以下のプログラムがある。フィボナッチ数を計算する単純なコードだ。コンパイラの性能や最適化性能を測るにはもっとちゃんとしたコードであるが、こんな単純なコードでもコンパイラや最適化オプションでどれほど性能が変わるかという参考にはなる。

#include<stdio.h>

int fib(int n){
  if(n < 2) return n;
  return fib(n-2) + fib(n-1);
}

int main(void){
  printf("%d\n", fib(48));
}

コンパイラのバージョン

gcc gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
clang Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
icc icc (ICC) 14.0.3 20140422

gccとclangはUbuntu14.04のaptにあるバージョン。iccのみ最新版を利用。

実行時間はtimeコマンドで取得したのでバイナリサイズのオーバーヘッドもあるが、その分フィボナッチ数を大きく取ることで誤差を少なくしている。

実行環境のマシンは以下のスペック

PC HP h8-1280jp
CPU Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
Mem 16GB

デフォルトでコンパイルした結果

コンパイラ バイナリサイズ 実行時間1 実行時間2
gcc 8539 36.30s 36.48s
clang 8619 36.45s 36.75s
icc 20400 27.39s 27.28s

デフォルトでコンパイルした結果、iccが3割ほど早い。だが、以下の結果を見る限り、iccのデフォルトオプションが以下のものなのかもしれない。マニュアルを読まずにテストしたことを後悔している。

最適化オプションとして「-O2」をつけた結果

コンパイラ バイナリサイズ 実行時間1 実行時間2
gcc 8545 19.23s 19.10s
clang 8619 27.35s 27.35s
icc 20400 27.22s 27.12s

clangとiccが同等速度となったが、gccがずば抜けて早い。昔はgccはマルチアーキテクチャ対応のために最適化性能が悪いと言われたものだがこれは早い。インテル製のコンパイラをよく利用していたが、今はもうgccが最適な気もした。

最適化オプションとして「-Ofast」をつけた結果

コンパイラ バイナリサイズ 実行時間1 実行時間2
gcc 10029 15.16s 15.07s
clang 8619 27.59s 27.27s
icc 20400 27.30s 27.20s

このオプションはgcc以外で有効になるか確認していないが、clangでは「-O2」と同程度であるため有効にはなっているのかと思う。やはりgccが早い。もうインテル製は消そうかな。古いライセンスなんて役に立たないことも判明した。

アーキテクチャとして「-Ofast -march=native」をつけた結果

コンパイラ バイナリサイズ 実行時間1 実行時間2
gcc 10029 15.66s 15.61s
clang 8619 28.22s 28.19s
icc 20400 28.34s 28.20s

このコードはアーキテクチャの命令セットでどうにかなるようなものではなかったので特に速度差は感じられなかった。念の為に「-march=corei7」を指定してみても同じであった。



このように、最適化オプションやコンパイラによって実行速度やバイナリサイズが大きく異なる。実行速度は最適化前と最適化後では倍以上の速度差が見られるし、コンパイラによってバイナリサイズが倍程度にもなる(バイナリサイズを削減するオプションは入れていない)。こんなシンプルなコードでもこのような差が出ることがわかっていただけたと思う。

なので現実的なコードであればもっと顕著な最適化が見られる上に、さらには自動並列化オプションなどを利用することでさらなる並列化の恩恵も受けられる。

それにしても、10年前の知識からインテル製が早いと勘違いしていたが、今はgccがかなり早いことを知れてよかった。普段使っている物もたまには性能試験をしてみるものだな。


GNU C COMPILER 増補改訂版 Manual&Reference

GNU C COMPILER 増補改訂版 Manual&Reference

GNU Make 第3版

GNU Make 第3版