fapws3マジ速い?件とweb.pyのプロファイルの話

fapws3がなにやらいい感じとのPostが目立つ今日この頃。 「最速」との言葉に弱い私もweb.pyに組み込んで試してみました。 web.pyのアプリの例は 以前Postしたもの を使います。

william-os4y's fapws3 at master - GitHub

web.py + fapws3

fapws3はWSGI対応なのでweb.pyにも簡単に組み込めます。 コード全体は Sandbox 上にありますので適当に見てください。 fapws3を固有部分のみ以下に示します。

import fapws._evwsgi as evwsgi
from fapws import base

if __name__ == '__main__':
    application = web.application(urls, globals()).wsgifunc()
    evwsgi.start("0.0.0.0", 8080)
    evwsgi.set_base_module(base)
    evwsgi.wsgi_cb(("", application))
    evwsgi.run()

web.py内、applicationクラスのwsgifunc()メソッドをコールしたものを fapws3のwsgi_cb()でコールバック登録します。 このあたりの書き方はfapws3サンプルにあったhello_world.py以下、 起動系のサンプルスクリプトがすべて同じ書き方になっているので それをそのまま使いました。 start()→set_base_module()→wsgi_cb()→run()のイメージですね。

ちなみにミドルウェアをかましたい場合は、以下のような感じです。 (リクエスト毎に"Hello Debug!"と出力するHelloMiddlewareミドルウェアを 追加しています。)

class HelloMiddleware(object):

    def __init__(self, application):
        self.application = application

    def __call__(self, env, start_response):
        web.debug("Hello Debug!")
        return self.application(env, start_response)

if __name__ == '__main__':
    application = web.application(urls, globals()).wsgifunc()
    application = HelloMiddleware(application)
    evwsgi.start("0.0.0.0", 8080)
    evwsgi.set_base_module(base)
    evwsgi.wsgi_cb(("", application))
    evwsgi.run()

fapwsのコア部分はCで記述されているので、ちょっとコード読みがめんどくさいです。 興味があれば読んでみてはどうでしょうか。(私はあきらめてしまいました。)

さて性能は?

ローカルから以下コマンドで測定してみました。 5回ほど実行したおおよその平均値です。fapws速い。

$ ab -n100 http://0.0.0.0:8080/    # "Hello web.py"
$ ab -n100 http://0.0.0.0:8080/a/ # DBアクセス+テンプレート
[web.py] db,template
Requests per second:    30.40 [#/sec] (mean)

[web.py] non db,template
Requests per second:    106.45 [#/sec] (mean)

[web.py + fapws3] db,template
Requests per second:    50.49 [#/sec] (mean)

[web.py + fapws3] non db,template
Requests per second:    176.88 [#/sec] (mean)

[django mysite]
Requests per second:    14.32 [#/sec] (mean)

自分のDjangoサイトも測定してみましたが、、、思いのほかひどい結果。 それほどアクセスが無いにせよもう少し何とかしたいな。 nginx/fapws使うとか、キャッシュを有効にするためにツールで 定期的にアクセスするとか。

web.pyアプリをプロファイリング

web.py遅い遅いと言われますが、どこが遅いのか おせっかいにもプロファイルして見てみようということで、 web.pyアプリにプロファイラを仕込んでみました。

cProfile and hotshot

cProfileの結果 を見ていただければわかるのですが、 accept()待ちしているところも実行時間として測定されるので、若干期待していた 値と異なる場合があります。一回の測定時間を短くしたり、accept()待ち箇所は 測定者側で無視する等の対策が必要です。 (もう少し良い方法がないか探してみます。) hotshotでの実行結果 の結果は 小数点以下3桁くらいの表示ではあまりプロファイルとして有効なデータにはなっていませんね。

line_profiler

line_profiler は行毎の実行時間を出力してくれるPythonモジュールです。 作者の方曰くhotshotの行毎の測定は時間が掛かりすぎる云々。 確かにhotshotは測定にも解析にも時間が掛かります。 line_profilerは軽い動作でプロファイルしてくれます。 まずはインストール。

$ sudo easy_install line_profiler

使い方は測定したいメソッドを@profileでデコレートしてkernprofを「-l」指定で 実行するだけです。 実行結果は以下のようになります。

$ kernprof.py -l code.py
$ ls
code.py    code.py.lprof
$ python -m line_profiler code.py.lprof
Timer unit: 1e-06 s

File: code.py
Function: GET at line 19
Total time: 0.233386 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    19                                               @profile
    20                                               def GET(self, username):
    21         1         4376   4376.0      1.9          users = db.select('hellouser', wh
    22         1           10     10.0      0.0          isFound = False
    23         2          100     50.0      0.0          for u in users:
    24         1           20     20.0      0.0              if u.name == username:
    25         1            6      6.0      0.0                  isFound = True
    26         1            7      7.0      0.0          if not isFound:
    27                                                       db.query("INSERT INTO hellous
    28         1       228867 228867.0     98.1          return render.hello(username, isF

File: code.py
Function: GET at line 31
Total time: 9e-06 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    31                                               @profile
    32                                               def GET(self):
    33         1            9      9.0    100.0          return "Hello web.py"

内部のライブラリのコードまではプロファイルしないようです。 今回の私の用途には合いませんでしたが、特定のミドルウェアだけ 測定する、等の使い方ができるのでいいのではないでしょうか。

web.pyが遅い理由は次回書いてみます。では。