10/1/11

Performance of Flask, Tornado, GEvent, and their combinations

When choosing a web framework, I pretty much have eyes set on Tornado. But I heard good things about Flask and Gevent. So I tested the performance of each and combinations of the three. I chose something just a little more advanced than a "Hello World" program to write - one that use templates. Here are the codes:

1, Pure Flask (pure_flask.py)
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def main_handler():
   return render_template('main_j2.html', messages="whatever",title="home")

if __name__ == '__main__':
   app.run(port=8888, debug=False)

2, Pure Tornado (pure_tornado.py)
import os.path
import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
   def get(self):
      self.render('main.html', page_title="", body_id="", messages="whatever",title="home")

settings = {
   "static_path":os.path.join(os.path.dirname(__file__),'static'),
   "template_path":"templates",
}
application = tornado.web.Application([
   (r"/", MainHandler),
], **settings)

if __name__ == "__main__":
   application.listen(8888)
   tornado.ioloop.IOLoop.instance().start()

3, Flask with Gevent (gevent_flask.py)
from gevent.wsgi import WSGIServer
from pure_flask import app

http_server = WSGIServer(('', 8888), app)
http_server.serve_forever()

4, Flask with Tornado (tornado_flask.py)
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from pure_flask import app

http_server = HTTPServer(WSGIContainer(app))
http_server.listen(8888)
IOLoop.instance().start()

5, Tornado with Gevent (gevent_tornado.py)
import tornado.wsgi
import gevent.wsgi
import pure_tornado

application = tornado.wsgi.WSGIApplication([
   (r"/", pure_tornado.MainHandler),
],**pure_tornado.settings)

if __name__ == "__main__":
   server = gevent.wsgi.WSGIServer(('', 8888), application)
   server.serve_forever()


I have 3 template files: main.html, layout.html, and form.html. main.html "extends" layout.html and "includes" form.html. The total size of templates is about 30kB.
The reason Flask and Tornado use different templates (main.html and main_j2.html) is that their template syntax is slightly different. Flask (jinja2) template use "{% endblock %}", "{% endfor %}", etc. while Tornado just use "{% end %}".

I tested performance (requests per second) using ApacheBench:
ab -n 1000 -c 4 http://localhost:8888/
and run it 5 times. The testing is done on a 6-year old dual-Opteron 254 server.

Here are the results:
pure_flask: 82 88 107 102 71
pure_tornado: 144 244 241 294 290
gevent_flask: 127 139 145 152 110
tornado_flask: 110 88 74 92 101
gevent_tornado: 328 555 177 273 153

Here are the averages:
pure_flask: 90
pure_tornado: 242
gevent_flask: 135
tornado_flask: 93
gevent_tornado: 297


As you can see, the Tornado implementation is significantly faster than Flask. Gevent makes Tornado faster, but not by a lot.

In the end, I like the straight-forward style of Tornado and not the Flask way to write large project (using blueprints), So I sticks with Tornado.

2 comments:

Artur Kaminski said...

Many thanks. It is not only benchmark, but also Python web servers review.

I was looking for a web server for url shortener and tornado looks great. Gevent dumps core on my VPS, but ran on piece of metal works well. I am in the middle of the investigation. now it is time for event dispatcher to send shortened links to disk in "non blocking" way.


Regards

Anonymous said...

It should be noted that flask's webserver is only meant for development and is not designed for production.