More info...

2013-10-16

たった30秒でMySQLをコンパイルする方法 rev.2

もう2年以上前になるが、以前「MySQL 5.5をわずか30秒足らずでコンパイルするためのテクニック」というエントリを書いた。 エントリに書いた内容はそれなりにコンパイルの高速化に寄与はするが、実は測定方法は正しくなかった。このことについて、いつも冷静さを失わない奥一穂氏から、いつもの冷静さで指摘を頂いた。


奥さんの言う通りである。指摘をもらってから気がついた。反省した。それからからずっと「まっとうにコンパイルして30秒を切る方法」を模索してきた。そしてついに、ccacheを使わずにまっとうにMySQL 5.5のコンパイルを30秒未満で実行することが出来たので、その方法を紹介しようと思う。

速いマシンを買う



いきなり身も蓋もない解決法だが、ぶっちゃけこれが一番効果的である。実行するべき処理が決まっていれば、最終的にCPUの実行速度によって処理時間が決まってしまう。

実は最近PCを新調したのだが、そのPCでコンパイルするとあっさりと目標を突破してしまった。剛よく柔を制す。力こそパワー!・・・だが虚しかった。

「CPUの性能の違いが、戦力の決定的差ではないということを教えてやる!」

そう言おうと頑張って来たのだが、ついにその台詞を言える日は来なかった。俺は負けたのだ。だから改めて言おう。

「CPUの性能の違いは、決定的な戦力差です。」

とは言え、マシンの性能向上によって様々な問題が解決してしまうのはIT業界の常である。今回はそのことについて身を持って知ることになった。これからも恐らく思い知らされるのだろう。だが、いつの日か私も言ってみたい。シャアと同じ台詞を。

ところで、新しいPCについては、また改めてエントリで紹介しようと思う。

可能な限りソフトウェアをチューニングする


ところで、最近新調したマシンのCPUはi7-3740QMである。このCPUでは、何も工夫なしで30秒を切ることはできなかった。そこで改めて言おう。

「CPUの性能の違いだけが、戦力の決定的差ではない」と。最終的に目標を達成するには、少しだけ工夫が必要である。その工夫の一つ目が、ソフトウェアのチューニングである。

具体的にはOSカーネル、libc、gcc、binutils等のソフトウェアが最高のパフォーマンスを発揮できるよう自らコンパイルするのである。これは前回のエントリを書いたときにも有効な方法であった。

だったら最初から全部コンパイルするOSを使えばいいじゃない。

そういう結論に至るのはごくごく自然なことであると言えよう。すなわち、新しいマシンにインストールすべきOSはGentoo Linuxということである。言うまでもないが、Gentooには自分で各種ソフトウェアをコンパイルするための環境はばっちり揃っている。というかコンパイルしなければ先には進めないと言うべきか。だが敢えて言おう。Gentooは夢を叶えてくれるディストリビューションである

最適化をしない


マシンパワーの次に重要な点は、MySQLをコンパイルするときに最適化オプションをオフにするということだ。具体的には-O0フラグを指定する。

ソフトウェアのコンパイルにおいて何が一番時間がかかるのか。それは最適化である。文法の解析などは最適化に比べればはるかに軽い処理であると言える。-O2と-O0では、ほぼ倍ぐらい時間に差が出てしまう。とはいえ、当然最適化オプション(-O2など)をつけてコンパイルされたバイナリのほうが実行速度は速い。従って、現実には、最適化オプションなしでのコンパイルは、あくまでも実行速度よりもコンパル速度のほうが重要だというニーズがある場合(例えばMySQLを改造して繰り返しデバッグする場合)などに役立つテクニックだと言える。そういうニーズがある場合には劇的にコンパイル速度が変わるので是非試して欲しい。

shell> export CFLAGS='-O0 -m64'
shell> export CXXFLAGS="$CFLAGS"

ちなみに、-O0により以前のマシンでは60秒を切ることはできた。(しかし、50秒の壁は突破できなかった。)

古いバージョンのgccを使う


これも先ほどの話に通じる部分があるのだが、新しいバージョンほど新しい最適化アルゴリズムが追加されており、コンパイルに時間がかかるようになっている可能性がある。ただし、バージョンが上がることでコンパイラ自身の最適化が行われることもあるので、一概に古い方が良いとは言えないのではないかという予想もある。また、64ビット版と32ビット版でも異なる性能特性になるのではないか。ということで最新バージョンのgccだけでなく、実際にいくつかのバージョンでコンパイルにかかる時間を計測してみた。

gcc-3.4 64bit
real    0m 41.03s
user    4m 42.76s
sys     0m 19.04s

gcc-3.4 32bit
real    0m 40.30s
user    4m 38.49s
sys     0m 18.48s

gcc-4.1 64bit
real    0m 26.90s
user    2m 59.49s
sys     0m 13.75s

gcc-4.1 32bit
real    0m 26.41s
user    2m 54.28s
sys     0m 13.39s

gcc-4.3 64bit
real    0m 29.37s
user    3m 14.23s
sys     0m 15.23s

gcc-4.3 32bit
real    0m 28.25s
user    3m 9.59s
sys     0m 14.92s

gcc-4.6 64bit
real    0m 32.46s
user    3m 40.90s
sys     0m 14.95s

gcc-4.6 32bit
real    0m 32.20s
user    3m 37.38s
sys     0m 14.62s

gcc-4.7 64bit
real    0m 32.57s
user    3m 41.75s
sys     0m 14.74s

gcc-4.7 32bit
real    0m 32.00s
user    3m 37.78s
sys     0m 13.44s

これをグラフ化したものが次の図だ!!


最高記録はなんと26.41秒。(GCC 4.1.2 / 32ビット)大幅に30秒を切ることに成功した。なお、いずれのバージョンでも、64ビット版よりも32ビット版のバイナリをコンパイルするほうが高速であった。

計測時はCPUは当然ながらフル回転していた。topの出力はこんな感じ。us + sy = 100なので完璧に使い切っている。無駄がなくて素晴らしい。

top - 00:30:17 up  3:27,  5 users,  load average: 9.47, 5.85, 3.09
Tasks: 239 total,   9 running, 230 sleeping,   0 stopped,   0 zombie
%Cpu(s): 93.9 us,  6.1 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:  32600520 total,  4431904 used, 28168616 free,   777940 buffers
KiB Swap: 67108860 total,        0 used, 67108860 free,  2031960 cached

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 9362 mikiya    20   0  230124 107148   4088 R 28.58 0.329   0:00.86 cc1plus
 9374 mikiya    20   0  212540  87752   2824 R 21.60 0.269   0:00.65 cc1plus
 9380 mikiya    20   0  208188  84284   2824 R 20.94 0.259   0:00.63 cc1plus
 9386 mikiya    20   0  194284  70136   2824 R 15.95 0.215   0:00.48 cc1plus
 9392 mikiya    20   0  190048  65484   2824 R 14.62 0.201   0:00.44 cc1plus
 9399 mikiya    20   0  159220  34692   2788 R 4.653 0.106   0:00.14 cc1plus
 9410 mikiya    20   0  154160  29372   2652 R 2.659 0.090   0:00.08 cc1plus
 9416 mikiya    20   0  152644  27516   2160 R 1.994 0.084   0:00.06 cc1plus

その他のコンパイラオプション


-O0はコンパイル時間を劇的に改善するが、他にも次の2つのオプションをつけることで若干の性能改善が見られた。

  • -pipe。これはプロセス間の情報の受け渡しに、ファイルの代わりにパイプを使うようにするというものだ。これによりオーバーヘッドが少なくなってコンパイル時間が気持ち早くなる。
  • -w。これは全ての警告を表示しないようにするもの。今回は時間の測定なので警告はどうでも良いので非表示にした。

また、最近「本の虫」で紹介されていたGentooを最速でブートせよ というエントリで、busyboxにすることで起動が早くなったというのを見たのでbusyboxを試してみたところ、若干の性能改善が見られた。

実際の計測は次のようなスクリプトで行い、3回のうちベストのものを結果として採用した。結果さえ分かれば良いので、余計な出力はすべて/dev/nullに叩きこんである。(そうすることで少しでも余計なことにCPUが奪われるのを避けるためでもある。)

for arch in 32 64
do
  export CFLAGS="-m${arch} -g0 -O0 -pipe -w"
  export CXXFLAGS="$CFLAGS"
  rm CMakeCache.txt
  cmake ..>/dev/null
  make clean
  time make -j 9 >/dev/null
  make clean
  time make -j 9 >/dev/null
  make clean
  time make -j 9 >/dev/null
done

禁断の果実、clang


さて、最近はどうもclangが勢いを増してきているようである。そして、clangはコンパイルが速いというもっぱらの評判だ。GNU支持者としてはGPLを排除しようという動きは非常に心苦しい限りだが、比較しないのはフェアではないので試してみることにした。その結果、確かに速かった。

clang 3.3 64bit
real    0m25.222s
user    2m46.394s
sys     0m10.417s

clang 3.3 32bit
real    0m25.701s
user    2m50.028s
sys     0m10.705s

なんと、25秒台が出てしまった。gccのベストに1秒以上の差をつけての勝利である。実際の利用シーンではほとんど誤差のようなものであるとは考えられるが、clangは確かに速かった。しかし、clangではbusyboxを使った場合に64ビット版コンパイル時にエラーが出るといった問題もあった(原因は調べていない)ので、やはり安定性という点ではまだ不安があるように思う。

バイナリの性能


最後に、コンパイラによる最適化の違いでどの程度性能に差が出るのかを示しておこうと思う。結論から言うと、やはり-O0で生成したバイナリはコンパイラに関わらずかなり遅いと言える。-O0はテスト時など、とにかく速くコンパイルしたい場合だけ使用するべきだろう。gccとclangの差はほとんど誤差に近い。


まとめ


今回、ようやく字面どおり30秒以下でMySQL 5.5をコンパイルすることが達成できた。やはり-O0の効果は絶大であり、そして速いCPUは正義である。コンパイル時間のチャレンジは面白いので、今後も引き続き行おうと思う。また進展があれば(何年後になるかはわからないが)報告したいと思う。

0 件のコメント:

コメントを投稿