2011年2月15日火曜日

Objective-Cの『遅さ』を計測したら、JavaやC++の5倍も遅かった

このエントリーをはてなブックマークに追加
Pocket

iPhoneやMacintosh OS Xのおかげで注目を浴びる機会が多くなったObjective-Cだが、漠然と「CやC++ほどではないが、実行速度は十分に高速」と思われている。しかし、動的言語なのでメソッド呼び出しが遅いとも言われる。

ところがThe Computer Language Benchmarks GameではObjective-Cは扱われていないし、そもそも「メソッド呼び出し」に特化してはいない。Objective-C推進者は、速度が大事な部分だけCのように書けば高速だと主張しており、その遅さについては秘密にしたがる。

隠されると何かあるのではないかと気になるのが人情だ。早速、C++、Objective-C、Java、Pythonでメソッド呼び出しに特化したベンチマークをとってみた。

1. ベンチマーク方法

ベンチマーク方法は簡単に、1変数、1メソッドのオブジェクトを作成し、ループして2,147,483,647回、同一のメソッドを呼び出した。メソッドはint引数が一つあり、int値を戻す。AMD機でUbuntu LinuxのGCC 4.4.3でコンパイルをし、実行を行った。

なお、今回は純粋にメソッド呼び出し速度をテーマとしているので、ソースコードはMercurialのリポジトリで公開する。もちろんソースコードの問題点の指摘や、他の言語での実装は歓迎だ。

2. ベンチマーク結果

オーソドックスに書いたObjective-Cのコードは34.69秒と、C++の5.5倍の時間がかかっている。Objective-Cのメソッド呼び出しの遅さは際立っている。

オブジェクトのメソッドをCの関数に変換(Objective-C IMP)すれば8.07秒と27%増しで済むが、それでも遅い。変換時間を除外しているのにである。

ただし、代表的スクリプト言語の一つであるPythonよりは44.8倍速いので、Objective-Cは十分速いと言えなくも無い。しかし、-serverオプションをつけて起動したJava HotSpotよりは、かなり遅い結果となっている。

2,147,483,647回の関数/メソッド呼び出し
経過時間 メモリ利用量
Java 1.6.0_22 (HotSpot) 6.32秒 11,000KB
C++ 6.33秒 716KB
Objective-C (IMP) 8.07秒 804KB
C++ (Virtual) 9.77秒 716KB
Objective-C 34.69秒 808KB
Scala 2.8.1 (@yasushia版) 35.88秒 28,000KB
Java 1.6.0_22 36.01秒 10,000KB
Scala 2.8.1 48.29秒 30,000KB
Go 8g 49.38秒 760KB
Lua JIT 2.0.0-beta6 58.00秒 812KB
JavaScript (Firefox 3.6) 2分25.0秒 -
Lua 5.1.4 18分12.0秒 796KB
Smalltalk (Pharo) 1.1 20分31.2秒 30,000KB
Ruby 1.9.2 23分50.9秒 2,876KB
Python 2.6.5 25分56.8秒 3,360KB
Smalltalk (Squeak) 3.9 44分21.8秒 25,000KB
Perl 5.10.1 44分31.9秒 2,428KB
PHP 5.3.2 2時間14分18.9秒 8,576KB
Ruby 1.8.7 3時間01分21.9秒 1,860KB

なお、メモリ消費量はtopコマンドで測ったので、かなり大雑把な数字だ。また、Cで同様の処理のコードを書くと、ほぼC++と同じ速度になる。

追記(2011/02/17 8:50):Rubyによるベンチマークを追加。

追記(2011/02/17 11:00):Smalltalkによるベンチマークを追加。ソースコードは「Smalltalkのtは小文字です」のループ回数を修正した。

追記(2011/02/17 16:00):Perlによるベンチマークを追加。

追記(2011/02/18 10:30):Java 1.6.0_22で実行した、Scalaによるベンチマークを追加。また、clang/llvmでC++とObjective Cの値を取り直し、改善が見られないのを確認。

追記(2011/02/18 14:30):Ruby 1.8.7によるベンチマークを追加。1.9.2との速度差については、@IT等を参照。

追記(2011/02/18 15:00):Smalltalk (Pharo) 1.1によるベンチマークを追加。

追記(2011/02/24 8:00):Go 8g release.2011-02-01.1 7463によるベンチマークを追加。

追記(2011/02/24 22:00):Luaと、Lua JITによるベンチマークを追加。

追記(2011/04/16 9:00):Scala 2.8.1 (@yasushia版)のベンチマークを追加。なお、JVMのバージョンは1.6.0_24-b07になっている。

3. Objective-Cのメソッド呼び出しは遅い

アプリ全体のパフォーマンスを評価するベンチマークではないので、Objective Cの全体への評価ではないが、遅い。

高速化手法Objective-C (IMP)も提示したのだが、利用するとObjective Cらしくなくなり、コードの見通しが悪くなるので全面的に使えるようなものではない(マイコミジャーナル)。ソースコード中のチューニング・ポイントを絞る事が重要になるようだ。

4. Objective-Cのインスタンス生成は連鎖的に遅くなる

クラスの初期化もメソッドの呼び出しが多発するので、この結果はObjective-Cのインスタンス生成も遅いことを意味する。予備的にインスタンス生成だけ100万回ループして行ってみたのだが、Objective-Cが0.2秒、Java HotSpotが0.01秒となっており、インスタンス生成が遅いとされるJavaよりさらに20倍も遅くなっている。

5. Objective-Cの速度は注意が必要

この結果とは反するが、JavaよりObjective-Cが遅いかというとそうではない。Javaはメモリー消費量が大きくなると、GCが発生して極端に速度を落とす傾向がある。メソッド呼び出しを伴わない部分は、恐らくObjective-Cの方が速いであろう。少なくともレスポンスは良くなるはずだ。

しかし、C++と比較するとObjective-Cが遅いという結論は動かないように思える。チューニング・ポイントは明確ではあるが、場合によってはプログラマ負荷がC++よりも高くなる可能性がある言語だと言える。

6. まとめ

メソッド呼び出しの遅さは、実はJavaも昔は指摘されていたことだ。Javaは文法が静的であるためか、JVMの進化とともに問題解消してしまったが、動的言語であるObjective-Cは解決不可能な弱点として残ってしまっているように思える。

Javaの方が生産性が高いし、C++の方が速度が出るとすると、Objective-Cは駄目な子に思えるかも知れない。しかし、Mac OS Xのシステム記述言語であり、動的言語の特性を生かしたプログラミングも可能であるため、OS Xで利用している限りは優位性は残る。LinuxやWindowsでは、C++のクラスはローダーで動的リンクを行う事はできないが、Mac OS XのObjective-Cでは可能だ。メモリ利用量も多くは無い。

ただ、Objective-CをUNIXやWindowsで利用しない理由になってしまうのも事実だ。動的言語だという特徴も、一般的なプログラマに魅力があるようではないようだ。PerlやRubyよりも人気言語になったとは言え、iPhoneやiPadの普及によるところが大きい(マイコミジャーナル)。Objective-Cは事実上のApple社製品専用言語として、今後も生き残っていくしか無いのかも知れない。

1 コメント:

etc さんのコメント...

Messageが使えて動的型付言語と同じく、委譲が簡単だったり、Messageの送り先がSelector通りのMethodである必要がないってのは、C++系統と比べて処理性能以上に評価できる点だと思いますけど。例えば、第三者が提供するGraphic Context系のClassを利用していました。あるとき、Graphic Contextを利用するある関数でBeizer曲線の利用が必要になりました。第三者が提供するGraphic Contextは、Beizer曲線に対応していません。こういう時、C++系統の言語であれば第三者のGraphicContext Classを継承し、既存の図形の組み合わせでBeizer曲線を描画できる新たなClassを作ります。ですが、Objective-Cの場合、Beizer曲線を描画するMethodと、残りの処理を全て他のObjectに投げつけるMethodの2個だけ定義したClassを作って対応するといった事ができます。この場合、第三者のClass以外でもBeizer曲線に対応していないClass全般に対して新たなClassを使いまわすことができます。C++でも継承ではなく明示的な委譲を記述することである程度使い回しの効果を得る事ができますが、移譲先のClassのMember関数全ての移譲処理を書かなければならず現実的じゃありません。また移譲先のObjectの変更にも弱くなります。こういう利便性は、大型のLibraryを構築した際Objective-Cの方が楽だなーと感じます(Objective-Cに限らずgoとかにも言える話ですが)。それから、C++やJavaの様にMember関数ではなくMessageとMethodにわかれている点を利用してMethodの遅延実行できるってのはかなり生産性が良いです。全体として行数が減る事になるので、C++やJavaと比べてそれほど保守性が落ちるわけでもないですよ。

コメントを投稿