2012年2月16日木曜日

Cython 素敵

Cython なるものを使い始めました。これはちょっと一言で表し切るのが難しいのですが、Python とほとんど同じ文法を使いつつも C 言語並みの実行速度を得る事が出来るようなプログラムのことです。実物を見てもらった方が早いと思うので、今日から取り組み始めた Project Euler の第9問目を題材にしてごく簡単に説明します。

問題はこうです:和がちょうど 1000 になるようなピタゴラスの三つ組の積を求めよ。

ひとまず、何も考えずに下のようなスクリプトを書きました。

#pe9_job_normal.py
def job():
    for i in range(1, 1001):
        for j in range(i+1, 1001):
            for k in range(j+1, 1001):
                if i**2 + j**2 == k**2 and i + j + k == 1000:
                    return i*j*k

#pe9_normal.py
import pe9_job_normal
print pe9_job_normal.job()

time python pe9_normal.py とした結果は
real    0m36.493s
user    0m36.172s
sys    0m0.046s
で、体感としてはちょっと待たされたという感じでした。

肝心の Cython を利用したコードは次の通りです。

#pe9_job_cython.pyx
def job():
    cdef int i, j, k
    for i in range(1, 1001):
        for j in range(i+1, 1001):
            for k in range(j+1, 1001):
                if i**2 + j**2 == k**2 and i + j + k == 1000:
                    return i*j*k

#pe9_cython.py
import pyximport; pyximport.install()
import pe9_job_cython
print pe9_job_cython.job()

元のスクリプトとの相違点は3カ所あります。1つはループ計算をする関数が .py ではなく .pyx ファイルに書かれていること。そのファイルの 2 行目では cdef int という C 言語ライクな型宣言がなされています。この .pyx ファイルを import pyximport することで pe9_cython.py にインポートしています。ただこれだけのことですが、実行結果は
real    0m0.381s
user    0m0.255s
sys    0m0.074s
となり、およそ 100 倍の速度を得ています。ループ計算中に頻繁に用いられる変数の型を決めてしまう事で、実行速度が劇的に改善される事が分かりました(むしろ Python はどんだけ遅いんだと思ってしまうくらい)。range 関数を使う場合は上のコードのようにループ変数を cdef することで最適化が行われるそうです。

Cython のドキュメントなどによると元々は Sage を開発する目的で作られたとのことで、確かに Python みたいな書きやすさと C に匹敵する速さがあれば開発スピードも実行速度も申し分ないのだろうと想像は出来ます。表面上は C に似ているとはいえ、コンパイルしなくていいというだけでも心理的な負担はかなり少ないです。

Cython については今日気まぐれで使い始めただけなので、まだ色々分かってません。Python のリストや辞書をどう扱ったらいいのかとか、pure C との使い分けをどうするかとか理解したいです。今のところは Python スクリプトを気楽に高速化するための便利システムという程度の認識ですが、今後はもっと入れ込んだりするのかも知れません。なんか分かったらまた書きます。

Python 関連の記事ばっかりになる悪寒がする orz

*追記 @ Feb. 17
Wikipedia の 関連ページ を見てみたらほとんど同じような内容が書かれていてショック……。先に確認しておけば良かった。

0 件のコメント:

コメントを投稿