
今出張で、サン・フランシスコに来ています。街はもうクリスマスムードで、楽しげな雰囲気がただよっています。期間限定のアイススケートのリンクが公園にできていたり、ngmoco:)のオフィスの入っているビルにもイルミネーションが飾られていたりします。
PyPy advent calendar に参加しています。@aodagからバトンを受け取りました。今日のこのネタはアドベントカレンダーの記事です。
PyPyはご存知でしょうか?その怪しげな(Pythonらしくイヤラシい)名前にもかかわらず、なんだかよく分からないパフォーマンスを叩き出しているPython処理系です。1.6でもすでにCPythonよりも早かったのですが、1.7ではさらに改善されています。
ですが、このPyPyよりも速いPythonの処理系が存在します。次のスクリーンショットはフィボナッチ数列を計算するベンチマークプログラムの結果です。それぞれの組みの最初の行が計算結果(F31)で、2行目が経過時間です。このベンチマークでは、比較対象としてCPython 2.7.2とPyPy 1.7を使っています。
PyPyはCPythonよりも8.6倍ほど高速であることが分かりますが、「それ」はPyPyよりも5倍ほど高速でした。オリジナルのCPythonと比べると、実に40倍の速度です。にわかには信じがたい結果です。
この秘密のPythonはPyPyに含まれています。Restricted Pythonというもので、略してRPythonと呼ばれます。Zopeには同じ名前のPython環境がありますが、これとは異なります。ベンチマークのコードを以下に貼り付けます。このブログではsmippleを利用しました。Ianさんありがとう!
だけど、そんなインタプリタ存在しないよ
ですが、RPythonインタプリタなどというものはどこを探してもありません。RPythonというのは、Pythonのサブセットの仕様名です。すべてのRPythonのプログラムはPythonやPyPy上で実行させることもできます。PyPyでは、もう一つ、translate.pyというツールを提供しています。このブログのキモはこのプログラムです。translate.pyについて紹介します。
RPythonってなに?
RPythonはPyPyのために作られたサブセットです。PyPyがPythonで書かれている、という話を聞いたことがある人もいるでしょう。ですが、PyPyのとんでもない速度はGuidoが開発しているCPython上で達成された結果ではありません。Pythonで書かれたPyPyのインタプリタのソースコードはtranslate.pyを使ってC言語やLLVM、CLR(.net)、Javaといったバックエンドのソースコードに変換されます。RPythonは軽量言語的な柔軟性を捨てる代わりに、速度を獲得した言語といえます。例えるなら乙女座のシャカですね。RPythonの結果はまさに天舞宝輪です。
py2exeなどのように、このネイティブコードやJavaや.netなどのプログラムを生成できるというところに興味があったので、アドベントカレンダーのネタ作りのためにちょっと触って見ました。
RPythonでコードを書くにはどうするの?
つぎのコードは、すべてのプログラマーにとって馴染みの深いコードサンプルのHello Worldです。
- target関数は、エントリーポイントを定義する特別な関数。
- __name__を使ったブロックはRPythonでは実行されないため、他のPythonインタプリタと互換性を維持するのに使える。
- エントリーポイントの関数は整数を返す必要がある。
つぎのコマンドを実行して、スタバにでも行ってコーヒーを飲んでいると、凄まじく効率の良いバイナリのプログラムが生成されます。
僕はまだ詳しくは調べていませんが、RPythonには速度に関するオプションがたくさんあります。Just-In-Timeコンパイラだったり、ガーベジコレクションのアルゴリズムを選択s地ありできます。上記のベンチマークの結果は単に-O2をつけただけで簡単に実行したものです。また、上記のコマンドラインではCPythonを使っていますが、PyPyを使うと、ちょっと時間の節約ができます。
RPythonプログラミングはエクストリーム・プログラミング
この「エクストリーム」は「エクストリーム・アイロン」的な意味ですお察しください。ほとんどのPythonのコードはそのままでは変換できないでしょう。なんだかC++でコードを書いているかのような気分になりますし、多くの落とし穴があります。ここでは解決方法がわかっているものに限定していくつか紹介します。
関数の中で他の関数を定義
関数定義をネストさせることはできません
多重継承
RPythonは多重継承をサポートしていません。メソッドのコードの共有をしたい場合には、_mixin_フラグを2つ目以降のクラスに付ける必要があります。super()関数を使うこともできません。ただし、多重継承をすることができないため、親クラス群をC3アルゴリズムで直列化することはないので、それほど重要ではありません。
公式サポートされているモジュールが少ない
公式には、os, math, time群だけがサポートされているようです。せめてreを・・・
@propertyデコレータ
プロパティというすばらしい機能を使うことができません。
メタプログラミング
__dict__プロパティにアクセスすることができないため、dir()関数が使えません。もしRPython用のテスティングフレームワークを作るのであれば、古のCppUnitのようにテストメソッドを1つずつ手で登録していくようなコードになる気がします。
関数の引数が、特定のデータ型にバインドされる
通常のPythonでは、変数が型情報を持つことはありません。そのため、C++のテンプレートのように、ジェネリックなアルゴリズムを記述することもできます。しかし、RPythonの変数は型情報を持ち、最初に割り当てられた時にバインディングが生成されます。これには関数の引数も含まれます。
RPythonの印象

なんだか難しいなぁ、と感じました。
最初に、「これが正規表現をサポートしたら、ちょっとした小さなツール開発で使えるかな、と考えました。そのため、pure Pythonで実装されていて、reと互換性を持つrxpyの移植に挑戦しました。しかし、凄まじく難しくて、うまく完成まで持っていくことができませんでした。上記の落とし穴も、この移植中に見つけたものになります。また、RPythonの最も厳しい点は、ビルド時にリソースを大量に消費するというものです。上記のHello Worldのビルドでも1.5分ぐらいかかりましたし、もしPyPyのような巨大なプロダクトをビルドするときは、メモリは4GB以上ないとダメ、だとか。
また、それほど詳しいドキュメントもありませんし、だれかがRPythonの仕様を保証してくれているわけでもありません。そのため、試行錯誤を繰り返して触っていく必要があります。しかし、将来的には、RPythonのアノテーションの仕組みをもっと詳しく知って、RPythonでのプログラミングを支援してくれる便利なライブラリが増えてきたりすると、もっと開発しやすくなるでしょう。
僕は兎にも角にも、このスピードのすごさを目の当たりにしてしまったため、少しずつでもRPythonを触って、RPythonでできることをどんどん広げていきたいと考えています。
PyPyアドベントカレンダーは、つぎは@yanolabさんの番です。
なお、このエントリーでちょっと言葉が難しいなあ、と感じられた方は、とても評判のよい、エキスパートPythonプログラミングという本をおすすめしております。とうか訳しました。よろしくね!