ISUCON の季節ですね。 ISUCON では慣習的に各言語で代表的なマイクロフレームワークが使われるのですが、 Python では今のところ Flask がずっと使われています。
Flask は確かに、簡単なサンプルアプリを書くときの見た目はマイクロフレームワークになっています。
しかし、構造的には沢山のフック、シグナルがあったりしていて、重量級の設計になっています。
Flask 本体と Werkzeug を合わせると数万行のサイズです。単なる Hello World アプリでも、数十の関数呼び出しが裏で動いています。
Bottle も、 Flask と同じくマルチスレッド対応で、スレッドローカルを使ったコンテキストスタックがある、拡張機能もあるフレームワークですが、構造は Flask よりも大分質素です。
ソースコードも1ファイル3000行代で、その分フレームワークのオーバーヘッドも Flask の半分程度になっています。
Hello アプリでちょっとした計測をしてみました。 MacBook Air 2013 Mid (Core i5 1.3GHz) で、シングルスレッド・シングルプロセスの Hello アプリを、 wrk -t1 -c1 でベンチマークします。 Python は CPython 3.4.1, Web サーバーは Meinheld (Github最新版) です。
wsgi:
$ wrk -t1 -c1 http://localhost:6000/
Running 10s test @ http://localhost:6000/
1 threads and 1 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 118.89us 46.59us 0.99ms 91.40%
Req/Sec 7.57k 622.67 9.90k 67.44%
71708 requests in 10.00s, 11.08MB read
Requests/sec: 7170.95
Transfer/sec: 1.11MB
Bottle:
$ wrk -t1 -c1 http://localhost:6000/
Running 10s test @ http://localhost:6000/
1 threads and 1 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 221.92us 678.78us 18.72ms 99.81%
Req/Sec 4.86k 418.08 6.44k 78.08%
46110 requests in 10.00s, 7.74MB read
Requests/sec: 4611.09
Transfer/sec: 792.53KB
Flask:
$ wrk -t1 -c1 http://localhost:6000/
Running 10s test @ http://localhost:6000/
1 threads and 1 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 476.59us 134.74us 1.71ms 79.35%
Req/Sec 2.11k 194.96 3.10k 65.86%
19967 requests in 10.00s, 3.35MB read
Requests/sec: 1996.71
Transfer/sec: 343.18KB
Avg Latency の差で見た場合、 Bottle は約 100μs, Flask は約 350μs が生 wsgi に対するオーバーヘッドになっており、トータルの性能としても倍以上の差になっています。
Bottle は Jinja テンプレートもサポートしているので、初期アプリが Flask だったとしてもそこそこ簡単に Bottle に切り替えられるはずです。
HTML の部分をキャッシュして、 Memcached から取得したデータをくっつけて返すだけのパスが沢山叩かれるようなケースでは、このベース部分のパフォーマンスが無視できない差になってくるはずです。
最後に計測にしたソースコードを載せておきます。お試しください。
import flask
import bottle
app = flask.Flask(__name__)
bottle_app = bottle.app()
@bottle_app.route('/')
@app.route('/')
def index():
return b"Hello, World"
def wsgi(env, start):
c = b"Hello, World"
start("200 OK", [('Content-Type', 'text/plain'), ('Content-Length', str(len(c)))])
return [c]
# 起動方法
# Flask: gunicorn -k meinheld.gmeinheld.MeinheldWorker -b :6000 app:app
# Bottle: gunicorn -k meinheld.gmeinheld.MeinheldWorker -b :6000 app:bottle_app
# wsgi: gunicorn -k meinheld.gmeinheld.MeinheldWorker -b :6000 app:wsgi