rubyバイナリの速さをベンチマークで比べてみる

折角いろんなバイナリを作られるようになったので、速さを比較してみる。
アーキテクチャi386 = 32ビット、x86_64 = 64ビット、ppc = 32ビットの3種類。ppc64のバイナリは動作させられないようで、rubyコンパイルもできなかった。
こんな最低限のソースから作ったものですら実行できないので、configureにコケルのです。

MyMac:~ chcoopu$ cat ppc64test.c 
int main(void){
  return 0;
}
MyMac:~ chcoopu$ gcc -arch ppc64 -O0 ppc64test.c -o ppc64test   
MyMac:~ chcoopu$ file ppc64test
ppc64test: Mach-O 64-bit executable ppc64
MyMac:~ chcoopu$ ./ppc64test 
-bash: ./ppc64test: Bad CPU type in executable
MyMac:~ chcoopu$ 


また、使用したコンパイラXcode 3.1に付属していたgcc-4.0とgcc-4.2の2種類。
コンパイルには

CC=gcc-4.0またはgcc-4.2

CFLAGS="-O2 -pipe -arch それぞれのアーキテクチャ"

状態で

./configure --prefix=インストール場所 --enable-shared

してコンパイルしたもの。一部configureファイルやMakefileでLDSHAREDの値を

cc -dynamiclib ...

から

$(CC) -arch それぞれのアーキテクチャ -dynamiclib ...

に変更してコンパイルしている。


そうしてできたバイナリを使って、ベンチマークを動かしている。ベンチマーク自体はRuby作者のMatzさんも取り上げているフラクタルベンチマーク実際のコード

これを以下のようにして連続3回実行している。

date; time /usr/local/ruby/ruby-1.8.6-i386-gcc-4.0/bin/ruby fractal.rb; date; time /usr/local/ruby/ruby-1.8.6-i386-gcc-4.0/bin/ruby fractal.rb; date; time /usr/local/ruby/ruby-1.8.6-i386-gcc-4.0/bin/ruby fractal.rb; date

それぞれのバイナリのインストールしたフォルダ名を別々にしてあるので、そこは変更する。フォルダ名にはアーキテクチャ名やコンパイラ名を入れてあるので、間違うことはないだろう。
以下3回の実行時間の平均。

*アーキテクチャ *コンパイラ 実行時間(s)
i386 gcc-4.0 6.248884
i386 gcc-4.2 6.295775
x86_64 gcc-4.0 5.873572
x86_64 gcc-4.2 6.141422
ppc gcc-4.0 15.694515
ppc gcc-4.2 18.666161

実行すると端末に「*」を使ってフラクタル画像が出力されるのだが、ppcバイナリの場合ははっきりと遅いことが感じられた。ロゼッタによる実行時変換に時間がかかっているんだろう。

また、折角Javaも入っているのでJRubyでも実行してみた。
JRuby環境変数JAVA_HOME以下にあるJava VMを使うので、JAVA_HOMEを切り替えており、確認のための出力もしている。使用したのはJava1.5.0の32ビットClient VM、同64ビットServer VM、Java1.6.0の64ビットServer VMの3種類。JRuby自体は全てで1.1.5を使用。

JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home; export JAVA_HOME; $JAVA_HOME/bin/java -client -version; date; time /usr/local/ruby/jruby-1.1.5/bin/jruby --client fractal.rb; date; time /usr/local/ruby/jruby-1.1.5/bin/jruby --client fractal.rb; date; time /usr/local/ruby/jruby-1.1.5/bin/jruby --client fractal.rb; date

こちらも同様に平均。

*JavaVMバージョン *起動VM 実行時間(s)
Java 1.5.0 32ビット Client VM 4.538105
Java 1.5.0 32ビット Server VM 3.580182
Java 1.5.0 64ビット Server VM 3.960026
Java 1.6.0 64ビット Server VM 3.147230

実行するとCのrubyとは違い、最初にフラクタル画像が出力される前に、突っかかるように一旦止まる。これはJava VMの起動にかかっている時間だろう。なので、ruby内で計測している上記実行時間よりも、実際の実行時間は長い。

総括

  • gcc-4.0で作ったバイナリはgcc-4.2で作ったバイナリよりも速い……気がする。
  • i386で32ビットなバイナリよりx86_64で64ビットなバイナリの方が速い……と思う。
  • JavaはClient VMよりもServer VMの方が速い……と言っていいと思う。
  • Java 1.6.0はJava 1.5.0よりも速い……のだろう。

といった辺りはほぼ言える。
rubyjrubyの比較は一概には言えない。今回のフラクタルベンチマークではjrubyの方が速かったが、単にこれだけかもしれない。
傾向として見えてくるほどに色んなベンチマークを色んな環境で試した訳ではないし、そもそも実アプリではないので参考程度にとどめるべきだし、最終的には自分の使いたいアプリで、どれが速いかを実測するべき。
なぜ今回比較したかというと、おもしろそうだったから。それだけ。

以下、実行時の生の出力のみ。
と思ったのだが、長過ぎるせいか途中できれるのでエントリ分割。

まとめていたら、Java 1.5.0のServer VMは、64ビットではなく32ビットで動作していたので、修正&64ビットの結果も取得。

サーバ/インフラを支える技術 読了

はてなの中の人が書いた本。

[24時間365日] サーバ/インフラを支える技術 ?スケーラビリティ、ハイパフォーマンス、省力運用 (WEB+DB PRESS plusシリーズ)

[24時間365日] サーバ/インフラを支える技術 ?スケーラビリティ、ハイパフォーマンス、省力運用 (WEB+DB PRESS plusシリーズ)


電車の中でしか読んでいなかったので、読み始めてから1ヶ月くらいかかったかな?読むの遅すぎ、反省。


内容は非常にわかりやすく、どのように安心、安全なシステムを構築・運用していくか、という話。
本当にわかりやすいので、ちょっとでもこの辺りに興味あったら読むべき。というか多くのIT技術者に読んで欲しい内容。
自分が構築・運用しなくても、知っている/認識しているかいないかで、できるシステム・アプリのできが変わると思う。


個人的に大絶賛



残念な部分

  • サービスのチューニング・最適化の話が出てくるが、サーバ/インフラ側での解決の話だけになり、アプリ側がアルゴリズム・実装・設定レベルで最適であるべきということが、あまりにも本文でさらっと出てくるだけだったこと。富豪的プログラミングは有りとはいえ、アプリ側を最適にするだけで10倍速くなったとかは普通にあるのだから、前提としてもっとはっきり書くべきだったんじゃないかな。#もちろんサーバ/インフラ側の変更でアプリの変更が必要になることもあるけど、ここでは置いておく。
  • この本では全部Linux上にソフトで組んだけどLinuxでなく他のOSでも専用HWとかでもできるよ、ってのがあまり見えてこないこととか。
  • 仮想化の話がほとんどなかったこと。VMware InfrastructureでActive/Stand-byやDR対応できるとかのレベル。
  • ディザスタリカバリ( DR )周りの話……は、まぁしょうがないか。
  • そうそう、バックアップの話とか。個人的には同一種類メディアはバックアップにならないよ派。つまりRAIDミラーやディスクコピーそのものはバックアップにならないと考える人なんで、この辺りの話も欲しいかなと。D2D2Tあたりが最近のトレンドかな?
  • さいごは個人的・宗教的問題なんだけど、いきなりGPL (と思われる)コードが出てくるのはやめて欲しい。疑似コードになっていれば問題ないんだけど、そのままは……

と、なんだか残念な部分の方が多く書いてしまったけれど、こんな些末な内容でしかケチがつけられないほど、いい本なんだYO!!ってことなんですよ。
だって、残念として挙げたものって、注意してあれば嬉しいけど、なくても全然致命的な問題にならないじゃない?


ちなみに、はてなでブログ始めたのはこの本を途中まで読んで、いいと思ったからってもの有ったりする。

ユニバーサルバイナリを作ってみる ruby編補足

昨日の検索では引っかかってこなかったファイルもユニバーサルバイナリ化しないといけない雰囲気。これ

MyMac:~ chcoopu$ file /usr/local/ruby/lib/libruby-static.a 
/usr/local/ruby/lib/libruby-static.a: current ar archive random library
MyMac:~ chcoopu$ 

Mach-O形式として出てこなかったから見落としていた。libruby-static.aを展開してみると

MyMac:libruby chcoopu$ ls -a
./	../
MyMac:libruby chcoopu$ 
MyMac:libruby chcoopu$ cp /usr/local/ruby/lib/libruby-static.a ./
MyMac:libruby chcoopu$ ar x libruby-static.a 
MyMac:libruby chcoopu$ ls
__.SYMDEF SORTED	hash.o			re.o
array.o			inits.o			regex.o
bignum.o		io.o			ruby.o
class.o			libruby-static.a	signal.o
compar.o		marshal.o		sprintf.o
dir.o			math.o			st.o
dln.o			numeric.o		string.o
dmyext.o		object.o		struct.o
enum.o			pack.o			time.o
enumerator.o		parse.o			util.o
error.o			prec.o			variable.o
eval.o			process.o		version.o
file.o			random.o
gc.o			range.o
MyMac:libruby chcoopu$

ほら、i386なバイナリが出てきた。本当はこのlibruby-static.aもユニバーサルバイナリにしないといけない。

MyMac:libruby chcoopu$ file *.o
array.o:    Mach-O object i386
bignum.o:   Mach-O object i386
class.o:    Mach-O object i386
compar.o:   Mach-O object i386
dir.o:      Mach-O object i386
dln.o:      Mach-O object i386
dmyext.o:   Mach-O object i386
enum.o:     Mach-O object i386
error.o:    Mach-O object i386
eval.o:     Mach-O object i386
file.o:     Mach-O object i386
gc.o:       Mach-O object i386
hash.o:     Mach-O object i386
inits.o:    Mach-O object i386
io.o:       Mach-O object i386
marshal.o:  Mach-O object i386
math.o:     Mach-O object i386
numeric.o:  Mach-O object i386
object.o:   Mach-O object i386
pack.o:     Mach-O object i386
parse.o:    Mach-O object i386
prec.o:     Mach-O object i386
process.o:  Mach-O object i386
random.o:   Mach-O object i386
range.o:    Mach-O object i386
re.o:       Mach-O object i386
regex.o:    Mach-O object i386
ruby.o:     Mach-O object i386
signal.o:   Mach-O object i386
sprintf.o:  Mach-O object i386
st.o:       Mach-O object i386
string.o:   Mach-O object i386
struct.o:   Mach-O object i386
time.o:     Mach-O object i386
util.o:     Mach-O object i386
variable.o: Mach-O object i386
version.o:  Mach-O object i386
MyMac:libruby chcoopu$ 

まぁここまでできていたら、単にこれもi386バイナリなファイルとx86_64バイナリなファイルを用意してくっつければいいだけ。

MyMac:libruby chcoopu$ lipo -create libruby-static.a.i386 libruby-static.a.x86_64 -output libruby-static.a
MyMac:libruby chcoopu$ 

ほいできた。


……しかし、よく考えたら名前の通り静的ライブラリだな、これ。arでファイル取り出せるし。
それじゃ、動的共有ライブラリで作り直すか。
いつものようにi386なバイナリ作る準備して、configure時に--enable-sharedオプションを付けるだけ。
すると、こんな感じで動的共有ライブラリが作られる。

MyMac:ruby-1.8.6-p287 chcoopu$ ls -l libruby*
-rw-r--r--  1 chcoopu staff  1276776 12  2 00:03 libruby-static.a
-rwxr-xr-x  1 chcoopu staff   844844 12  2 00:03 libruby.1.8.6.dylib*
lrwxr-xr-x  1 chcoopu staff       19 12  2 00:03 libruby.1.8.dylib@ -> libruby.1.8.6.dylib
lrwxr-xr-x  1 chcoopu staff       19 12  2 00:03 libruby.dylib@ -> libruby.1.8.6.dylib
MyMac:ruby-1.8.6-p287 chcoopu$ file libruby.1.8.6.dylib 
libruby.1.8.6.dylib: Mach-O dynamically linked shared library i386
MyMac:ruby-1.8.6-p287 chcoopu$ 

そしてまたお決まりのようにx86_64バイナリを--enable-sharedで作ってユニバーサルバイナリを作り出すと。

MyMac:ruby-1.8.6-p287 chcoopu$ file libruby.1.8.6.dylib
libruby.1.8.6.dylib: Mach-O universal binary with 2 architectures
libruby.1.8.6.dylib (for architecture i386):	Mach-O dynamically linked shared library i386
libruby.1.8.6.dylib (for architecture x86_64):	Mach-O 64-bit dynamically linked shared library x86_64
MyMac:ruby-1.8.6-p287 chcoopu$

これをインストールすれば、おわり。

64ビットなrubyを野良ビルドする

何も考えずに本家からソースをダウンロードしてコンパイル&インストールすると

MyMac:~ chcoopu$ file /usr/local/ruby/bin/ruby 
/usr/local/ruby/bin/ruby: Mach-O executable i386
MyMac:~ chcoopu$ 

といった感じでi386アーキテクチャ用バイナリ、つまり32ビット環境でも動作するバイナリが作成される。PowerPCSPARCEM64T/AMD64なら64ビットモードで動作している時は32/64ビットのバイナリ両方を同時に実行できるので特に問題ないし不思議でもない。
Max OS X付属のrubyなら

MyMac:~ chcoopu$ file /usr/bin/ruby 
/usr/bin/ruby: Mach-O universal binary with 2 architectures
/usr/bin/ruby (for architecture ppc7400):	Mach-O executable ppc
/usr/bin/ruby (for architecture i386):	Mach-O executable i386
MyMac:~ chcoopu$ 

PowerPCIntel両方で動作するユニバーサルバイナリだとわかる。
ppc7400ということは、おそらくPowerPC 7400シリーズのこと。ということはG3かG4以降のバイナリということか。まぁ自分で使わないからどっちでもいいけど。
でも、実行バイナリではなくライブラリを調べてみると

MyMac:~ chcoopu$ file /System/Library/Frameworks/Ruby.framework/Versions/Current/usr/lib/libruby.1.dylib 
/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/lib/libruby.1.dylib: Mach-O universal binary with 4 architectures
/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/lib/libruby.1.dylib (for architecture ppc7400):	Mach-O dynamically linked shared library ppc
/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/lib/libruby.1.dylib (for architecture ppc64):	Mach-O 64-bit dynamically linked shared library ppc64
/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/lib/libruby.1.dylib (for architecture i386):	Mach-O dynamically linked shared library i386
/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/lib/libruby.1.dylib (for architecture x86_64):	Mach-O 64-bit dynamically linked shared library x86_64
MyMac:~ chcoopu$ 

PowerPCも32ビット(ppc7400)、64ビット(ppc64)バイナリの2種類入っており、Intelの方も32ビット(i386)、64ビット(x86_64)バイナリの両方入った、合計4種類のバイナリの入ったユニバーサルバイナリだとわかる。実行バイナリに両アーキテクチャの64ビット版が入っていないのは何か理由があるのかな?

とゆーわけで、普通に使える64ビットなrubyバイナリはない訳だ。
なので、自分で作ってみようという話。


x86_64向けにバイナリを作れば64ビットバイナリになるので、そのようにする。特に-m64とかは必要ではないみたい。
gccへのオプションとして-arch x86_64を付けるだけ。x86_64の代わりにi386を指定すれば今までと同じバイナリになる……が、指定する意味はないな。ppcやppc64も選択できるが、まぁ必要な人だけ、ドゾー。

で、大抵のソフトはMakefileの中でCC、CXXでCコンパイラC++コンパイラの指定をすることが多い。同時にCFLAGS、CXXFLAGSでCコンパイラC++コンパイラに対するオプションを指定することになる。

rubyコンパイルする時にもCFLAGSに-arch x86_64を指定すればOKかなと。
指定してconfigure、makeすると見事64ビット対応のrubyができる。

MyMac:~ chcoopu$ file /usr/local/ruby/bin/ruby 
/usr/local/ruby/bin/ruby: Mach-O 64-bit executable x86_64
MyMac:~ chcoopu$ 
MyMac:~ chcoopu$ /usr/local/ruby/bin/ruby -v
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-darwin9.5.0]
MyMac:~ chcoopu$ 

できるのだが、作っている途中で部分的に文句を言われる。

ld warning: in bytecode.o, file is not of required architecture

その結果何も拡張していないruby本体は動作するものの、添付ライブラリ辺りでこけてしまう。

MyMac:rubygems-1.3.1 chcoopu$ /usr/local/ruby/bin/ruby setup.rb 
/usr/local/ruby/lib/ruby/1.8/i686-darwin9.5.0/thread.bundle: Failed to load /usr/local/ruby/ruby/lib/ruby/1.8/i686-darwin9.5.0/thread.bundle (LoadError)
	from /usr/local/ruby/lib/ruby/1.8/thread.rb:5
	from ./lib/rubygems.rb:10:in `require'
	from ./lib/rubygems.rb:10
	from setup.rb:22:in `require'
	from setup.rb:22
MyMac:rubygems-1.3.1 chcoopu$ 

というわけで、どこが問題なのかを確認してみる。
ソースの中の$RUBY_SOURCE/extディレクトリ以下にある拡張ライブラリのソースをコンパイルしてできたオブジェクトファイル( .o )は64ビットバイナリ。

MyMac:thread chcoopu$ file thread.o 
thread.o: Mach-O 64-bit object x86_64
MyMac:thread chcoopu$ 

それに対して$RUBY_SOURCE/.ext/i686-darwin9.5.0ディレクトリ以下にあるのは何故かi386な32ビットバイナリ。

MyMac:i686-darwin9.5.0 chcoopu$ file thread.bundle 
thread.bundle: Mach-O bundle i386
MyMac:i686-darwin9.5.0 chcoopu$ 

つまりオブジェクトファイルをリンクする時に64ビットバイナリではなくなっているみたい。
ここでどうやってリンクしているかMakefileを確認してみると

LDSHARED = cc -dynamic -bundle -undefined suppress -flat_namespace

となっている。この呼び出されているccというのは/usr/bin/ccであり実体はgccである。

MyMac:~ chcoopu$ which cc
/usr/bin/cc
MyMac:~ chcoopu$ ls -l /usr/bin/cc 
lrwxr-xr-x  1 root  wheel  7 10 22 00:14 /usr/bin/cc@ -> gcc-4.0
MyMac:~ chcoopu$ 

gccなら、コンパイル時と同じにオプションを渡してやればいいわけ。
今回はMakefileを変更するのではなく、Makefileを生成するためのconfigureを変更。しかも手抜き変更。
ccをgcc-4.0に固定しないように変更+x86_64用の設定。
※そのままMakefileに展開されるので、変数の書き方はMakefile形式で。

MyMac:ruby-1.8.6-p287 chcoopu$ diff configure configure.ORG
16520c16520
< 	darwin*)	: ${LDSHARED='$(CC) -arch x86_64 -dynamic -bundle -undefined suppress -flat_namespace'}
---
> 	darwin*)	: ${LDSHARED='cc -dynamic -bundle -undefined suppress -flat_namespace'}
MyMac:ruby-1.8.6-p287 chcoopu$ 

この状態でconfigure、makeをし直し。一度makeした後ならmake distcleanしてからconfigure、makeをすると。
そうすると、今度はちゃんと64ビットバイナリになった。

MyMac:i686-darwin9.5.0 chcoopu$ file thread.bundle 
thread.bundle: Mach-O 64-bit bundle x86_64
MyMac:i686-darwin9.5.0 chcoopu$ 

そして、拡張ライブラリでもこけなくなった。めでたしめでたし。