2011-05-01

pypyで何も考えずにpythonを高速化する

Python公式のCPythonよりも高速という噂のpypyPython2.7.1相当の機能を実装したというので、その実行速度を測定しました。



■インストール(OSX)

pypyのインストールは非常に簡単で、本家からbzipをダウンロードして解凍したらできあがりです。


$ tar xjvf pypy-1.5-osx64.tar.bz2
$ cd pypy-c-jit-43780-b590cf6de419-osx64
$ ls
LICENSE README bin include lib-python lib_pypy site-packages

binの中にpypyというコマンドがありますので、それを実行すると起動します。


$ bin/pypy

Python 2.7.1 (b590cf6de419, Apr 30 2011, 03:30:00)
[PyPy 1.5.0-alpha0 with GCC 4.0.1] on darwin
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``how to construct the blackhole
interpreter: we reuse the tracing one, add lots of ifs and pray''
>>>>

>が3つではなく4つ出てくるところが、公式CPythonよりも強そうですよね!



■実測

実際にPython標準のパフォーマンス測定ツールtimeitで、実行速度を比較してみました。

比較のため、OSX上のCPython2.6.1, 2.7,1とpypyを使いました。



まずは単純に配列を作る処理。


------- CPython 2.6.1
>>> import timeit
>>> t = timeit.Timer(stmt="[x*x for x in xrange(1000)]")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
184.11 usec/pass

------- CPython 2.7.1
>>> import timeit
>>> t = timeit.Timer(stmt="[x*x for x in xrange(1000)]")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
153.30 usec/pass

------- pypy 1.5
>>>> import timeit
>>>> t = timeit.Timer(stmt="[x*x for x in xrange(1000)]")
>>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
120.95 usec/pass


CPython2.7は2.6よりもだいぶ速いですが、それをさらにpypyが上回る速度です。




次に、集合(set)の中の検索


------- CPython 2.6.1
>>> t = timeit.Timer(setup="a=set([x*x for x in xrange(100)])", stmt="[(x in a) for x in xrange(100)]")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
23.41 usec/pass

------- CPython 2.7.1
>>> t = timeit.Timer(setup="a=set([x*x for x in xrange(100)])", stmt="[(x in a) for x in xrange(100)]")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
19.59 usec/pass

------- pypy1.5
>>>> t = timeit.Timer(setup="a=set([x*x for x in xrange(100)])", stmt="[(x in a) for x in xrange(100)]")
>>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
11.97 usec/pass

これも結果は同様に圧倒的にpypyが高速。(2.7の2倍に迫る速度!)



こんなに簡単に高速化できるなら、どこかで試しに使ってみたくなりますよね。

2011-02-08

Ajaxを補足するためのPythonによる手軽なマルチスレッド処理

Webアプリの開発で、重い処理はAjaxで別途処理して取得するという手段が取れない場合があります。

「画面の一部で矢印がぐるぐる回って待たせるのではなく、本当に一瞬で全部表示したい」

などの場合です。

そんなときにお手軽に使えるテクニックとして、サーバー処理のマルチスレッドによる並列化があります。
Pythonのマルチスレッドでは、高度なCPUの並列処理はできませんが(グローバルインタプリタロックと言います)、少なくともI/O待ちの間別の処理を流す程度の事は可能です。[参考]
その特徴を生かして、Webアプリのレスポンスを速めることができます。


これを使う場面ですが、たとえば、以下のようなものがあります。



この画面はうちの会社で作っている特許分析WEBアプリのBiz Cruncherというもので、特許の詳細な内容を表示しているところです。


特許の閲覧というのは企業知財部では古来から「手めくり」と呼ばれ、いかに速く大量の特許の内容を把握するかに心血を注ぎます。
そのため、図などは一瞬で見れるべきだし、電子的な画面であろうと、一瞬で次々表示されなければストレスを感じるものです。


そういう場合は付属情報はAjax化で別リクエストにするにしても、必須の要素だけは泣く泣く1リクエストに押し込んで、極限まで速度を向上させる必要があります。

たとえば、上の特許詳細画面では時間のかかる処理として、テキストマイニング、画像の生成、詳細テキスト(長文)の取得、引用情報・経過情報の取得 etc.をそれこそ一瞬で行う必要がありました。

改良前はこの表示に2.8秒かかっていたのですが、それでも不満ということで、冒頭のマルチスレッド化を行って0.7秒にまで縮めることができました。

具体的には以下の関数 call_async を使います。

import threading
def call_async(target, args=None, kwargs=None):
u"""
関数非同期呼び出しツール。
関数を別スレッドで呼び出して、その後結果を取得できます。

#使用例
r = call_async(f,args=(5,))
#ここで別の処理を片付けます。
r["thread"].join() #スレッドが終わるのを待ちます。
if "result" in r:
print "RESULT", r["result"]
else:
print "ERROR", str(r["exception"])
"""
res = {}
def f(*inargs, **inkwargs):
try:
inargs[-1]["result"] = target(*(inargs[:-1]), **inkwargs)
except Exception, e:
inargs[-1]["exception"] = e
res["thread"] = threading.Thread(target=f, args=(args+(res,)), kwargs=kwargs)
res["thread"].start()
return res



この関数を使うと、関数呼び出しを別スレッドにしながら、その返り値を取得することもできます。
使用方法は上記関数のコメント内にありますが、以下のように、先に遅い処理を実行開始しておき、平行してその他の処理を済ませたあとで、ゆっくりと結果を取得します。

#使用例 slow1~3という遅い処理の関数がある場合

#まず冒頭で遅い処理を別スレッドで実行開始しておきます。
r1 = call_async(slow1,args=(5,)) #遅い処理1を開始!
r2 = call_async(slow2,args=("ABC","DEF")) #遅い処理2を開始!
r3 = call_async(slow3,args=([1,2,3,4])) #遅い処理3を開始!

#ここでその他の処理を片付けます。

r1["thread"].join() #スレッドが終わるのを待ちます。
r2["thread"].join() #スレッドが終わるのを待ちます。
r3["thread"].join() #スレッドが終わるのを待ちます。

if "result" in r1: #slow1の結果を使う処理
print "RESULT 1", r1["result"]
else:
print "ERROR 1", str(r1["exception"])

if "result" in r2: #slow2の結果を使う処理
print "RESULT 2", r2["result"]
else:
print "ERROR 2", str(r2["exception"])

if "result" in r3: #slow3の結果を使う処理
print "RESULT 3", r3["result"]
else:
print "ERROR 3", str(r3["exception"])


こうすることで、I/O待ちの発生する処理をいくつも並列で実行でき、実行時間の効果的な短縮を実現できます。

こうした手段としてPython標準のmultiprocessingが検討にあがりますが、あれはより高度な分散処理用ですので、関数の結果を取得しようとすると、とたんに大仰なものになります。

また、もちろんですが、この秒数の短縮で効果的なのはJavascriptの最適化もあるわけです。
それをした後に、さらにどうしてもサーバー側も高速化しないといけない場合は、このお手軽なマルチスレッド化も考慮するとよいと思います。

2010-12-04

PythonのUnicode->UTF-8変換はSJISやEUCへの変換よりもちょっと速い

想像していた通りですが、UnicodeをUTF-8に変換するのは、SJISやEUCに変換するよりもちょっと速いことがわかった。




>>> import timeit
>>> def unicode_henkan(c):
... t = timeit.Timer("a.encode('%s')" % c, "a=u'\u3042'*10000")
... print t.timeit(number=1000)
...
>>> unicode_henkan("utf-8")
0.075012922287
>>> unicode_henkan("sjis")
0.19597196579
>>> unicode_henkan("eucjp")
0.146716833115
>>> unicode_henkan("cp932")
0.21554517746
>>>


ちなみに、timeitは与えられた計測対象のプログラムを内部でpythonのプログラムとして解釈する際に、文字コードのことをあまり考えてくれないようなので、

t = timeit.Timer("a.encode('%s')" % c, "a=u'あ'*10000")

のように書くとUnicodeErrorになります。

2010-09-14

関数呼び出しをタプル形式にして遅延させる関数

関数呼び出しをタプルの情報にして保存するしくみを作った。
これを使えばWEBアプリでリクエストをまたいで関数呼び出しを遅延させることができるようになります。
関数呼び出しをタプル形式にして、memcacheなどに保存しておき、次に来たときに本当に呼び出したり。

# coding: utf-8
import inspect
import imp

def freeze_func(func, *args, **kwargs):
u"""
関数呼び出しを保存可能なタプル形式に変換する。
"""
m = inspect.getmodule(func)
return m.__name__, func.__name__, args, kwargs

def __importer(m, path):
a = imp.find_module(m[0], path)
b = imp.load_module(m[0], *a)
if len(m) > 1:
return __importer(m[1:], b.__path__)
else:
return b

def _importer(modname):
modname = modname.split(".")
return __importer(modname, None)



def unfreeze_func(info):
u"""
タプル形式で保存された関数呼び出しを関数に復元して実行する。
"""
b = _importer(info[0])
f = getattr(b, info[1])
args = info[2]
kwargs = info[3]
return f(*args, **kwargs)

if __name__ == "__main__":
import re
info = freeze_func(re.sub, r"[A-Za-z]", "*", "5-3-1 Asakusabashi")
print info # -> ('re', 'sub', ('[A-Za-z]', '*', '5-3-1 Asakusabashi'), {})
print unfreeze_func(info) # -> "5-3-1 ************"

2008-05-24

Djangoでblogっぽいのを作ったので移動します

Djangoでblogっぽいのを作ったので移動します。RSSも吐いておりますので宜しくお願いします。
新TekTekBlog

移動の理由:
1. このBloggerでは一切のJavascriptなどが貼れないので不便。アクセス解析の類も一切無理。
2. データのエクスポート機能もないので、書き溜めても外部に効率的に持ち出せない。
3. 自分で作って自分で設置するほうが面白い。

2008-05-23

はてブのホットエントリをなんとなーく分類するPythonスクリプトサンプル

昨日作った、Ward法のPythonバインディングを使って、はてなブックマークのホットエントリをなんとなーく分類するPythonスクリプトのサンプルを書いてみた。

サンプルプログラムは例によって、CodeReposに置きました。

試しに今の時点(5/23 深夜2:06)のホットエントリを分類した結果は以下のような感じです。
なんとなーく、分類されているような雰囲気。
もっと各エントリの情報を取得すれば精度は上がるんでしょうが、今の所はホットエントリのRSS一枚から取得できる情報(今の所タイトル中の名詞と、タグの単語)だけを使って分類しています。

----- 以下、その結果 -----

Group [ 50 ] --------------------
「トレードオフの概念は日本に無いのか」 三菱東京UFJ銀のシステム一本化報道に思う:NBonline(日経ビジネス オンライン)
「トレードオフの概念は日本に無いのか」 三菱東京UFJ銀のシステム一本化報道に思う:NBonline(日経ビジネス オンライン)
なぜか変換できない vs. なぜか変換できる:最近の「MS-IME」は目に余る――よろしい、ならば「ATOK」だ (1/4) - ITmedia +D PC USER
今日から始めるデジカメ撮影術:第97回 一眼レフとボケの関係 (1/3) - ITmedia D LifeStyle
NHK「クローズアップ現代」児ポ法単純所持処罰に絞って特集 - フィギュア萌え族(仮)犯行説問題ブログ版・サブカル叩き報道を追う
<ネットカフェ難民>「しんどい」と訴える妊婦まで 100人の実態調査(毎日新聞) - Yahoo!ニュース

Group [ other ] --------------------
Steve Jobs氏のようにプレゼンテーションをする方法 - builder by ZDNet Japan
調査結果に基づいて合成「人々が最も不愉快になる曲/最も聴きたい曲」:試聴可能 | WIRED VISION
グーグル、検索アルゴリズムを少しずつ明らかに:マーケティング - CNET Japan

Group [ 59 ] --------------------
愛し合うってすばらしい、セックスすると得られる10の効能 - GIGAZINE
はてなクラブ - はてな
十字軍はバカに勝てるか - モジモジ君の日記。みたいな。
「宅配寿司 銀のさら」の自虐CM | DIGITAL DJ
変な人があまりにも酷すぎる件について
高学歴のお嬢様をお持ちの方、教えて下さい - 発狂小町
目を開いているのに閉じていると感じる錯覚作成法 | 医学都市伝説

Group [ 60 ] --------------------
ケーキを売ればいいのに - 福耳コラム
落合博満ガンダム宣言
電撃速報!! Amazonを倉庫代わりにしていた転売厨終了のお知らせ
『らき☆すた』を見た非オタの友人が衝撃の一言 - GilCrowsのペネトレイト・トーク

Group [ 64 ] --------------------
世界のPhotoshopチュートリアル トップ50 - ITクオリティ
光源、光線、光跡を表現するPhotoshopのブラシ -2XGU.NET | コリス
歌舞伎町で遇ったギークなタクシー運転手の編み出した、群衆の英知を活かした馬券購入法 - 雑種路線でいこう

Group [ 68 ] --------------------
人間関係が苦手で社会に適合できない人向けのDVDソフト「ミテルだけ」が登場 - GIGAZINE
YouTubeから削除された動画の墓場「YouTomb」が登場、日本のアニメの削除件数は圧倒的 - GIGAZINE

Group [ 72 ] --------------------
AIR-users.jp - 日本の AIR ユーザのためのハブサイト
js-users.jp - 日本の JavaScript ユーザのためのハブサイト
perl-users.jp - 日本の Perl ユーザのためのハブサイト

Group [ 75 ] --------------------
痛いニュース(ノ∀`):「美少女ゲーム(エロゲ)・アニメは、人間性を破壊し少女殺害事件につながる。販売規制を」…民主党女性議員が請願
「美少女ゲーム・アニメをする人は心を破壊され、人間性を失っているので規制すべき」と主張するトンデモ請願が参議院に - GIGAZINE
痛いニュース(ノ∀`):「ジョジョの奇妙な冒険」、中東で「日本が侮辱」「テレビ局爆破しろ」と批判殺到→集英社、原作の第3部など出荷停止

Group [ 76 ] --------------------
人工知能の叛乱
Gmailの検索オプションまとめ | IDEA*IDEA
マックユーザがインストールしているアプリケーション - soundscape out
[okyuu.com] ソーシャルITメディア
透過をきれいに使ったウェブデザインいろいろ - DesignWalker
特集:Firefox 3とFirebugで始めるJavaScript開発|gihyo.jp … 技術評論社
PHP コード最適化 Best Practices 63+ - カタコト日記

Group [ 77 ] --------------------
「ジョジョの奇妙な冒険」が中東で非難 - 社会ニュース : nikkansports.com
「コーラン読み殺害指示」日本アニメ、中東で非難 - MSN産経ニュース
asahi.com:トヨタ、「カイゼン」に残業代 業務と認定、来月から - ビジネス

Group [ 78 ] --------------------
ヒトラー名言集:アルファルファモザイク
傷つきやすい人はどうすれば強くなる?:アルファルファモザイク
ウマーな牛スジカレーの作り方:アルファルファモザイク
ねとらぼ:米WIREDに「やる夫」の特大AA掲載 ひろゆき氏インタビューも - ITmedia News
1日500アクセス以上のブログは全ブログの●%:Garbagenews.com
@nifty:デイリーポータルZ:50年前の新聞広告

Group [ 79 ] --------------------
MOONGIFT: � ブラウザベースのSubversion管理ツール「USVN」:オープンソースを毎日紹介
CSSで背景画像をリサイズするテクニック:phpspot開発日誌
ナムコの成果主義、有能なゲーム開発者が報われない開発現場

2008-05-22

Ward法のPythonバインディングを作りました。

先日から作っていたクラスタリング(Ward法)のPython用バインディングを作りました。Pythonから一応つないで使えるようになりました。

ソースコードの取得とインストール。


$ svn export http://svn.coderepos.org/share/lang/cplusplus/misc/clustercpp
$ cd clustercpp/python/wardcluster
$ python setup.py build_ext --swig-cpp
$ sudo python setup.py install

サンプル実行。
引き続きディレクトリclustercpp/python/wardcluster内で以下を実行してください。

$ python sample.py
1 + 0 => 4 ( 0.244948974278 )
3 + 2 => 5 ( 0.412310562562 )
5 + 4 => 6 ( 4.80645253132 )
$

ちなみに、今上で動いたsample.pyは以下のようなスクリプトで、4本のベクトルをクラスタリングしています。
from wardcluster import Matrix, DoubleVec, Ward

m = Matrix()
m.append(DoubleVec([1.0, 2.0, 3.0]))
m.append(DoubleVec([1.1, 2.2, 2.9]))
m.append(DoubleVec([0.1, 1.2, 5.0]))
m.append(DoubleVec([0.3, 1.0, 5.3]))

w = Ward()

history = w.cluster(m, 4) #この4ってのは、ベクトルの本数

for h in history:
print h.index1, "+" , h.index2, "=>", h.newindex , "(", h.distance ,")"


メモ--------
* setuptoolsを使ったオシャレなモジュールにするのは断念。(swigの結果ファイルとの調整が難しかった)
* swigのtypemap?とか、そういうのはむずかしくて分からないので、めんどくさいインターフェース。
* あくまで実験的なものですよ。
* 相変わらずswigを理解できていない。
* 今回ベクトルの集合を表すために内部でstd::vector<std::vector<double> >を使うようにしたので、パフォーマンスが落ちています。(将来もとのstd::vector<double>*を使って渡す方式に戻したいものです。。。)