Following yesterday’s experiments, some asked for a more comprehensive test including some more frameworks. Flask, Tipfy and the templating engine Jinja2.
It was my first experience with these, Flask got my attention, it seems like a slick microframework. Jinja2 was deceptive because of its limited template syntax way too far from Python. Tipfy didn’t really please me, but looking at its performance figures, I might now take a second look at this one.
Lets first take time to look at the new implementations. Getting a feeling on how they differs from the previous.
from google.appengine.ext.webapp import util
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/flask/<numA>/<numB>")
def hello(numA,numB):
return render_template('flaskTestTemplate.html', nums=range(int(numA)), numB=numB)
def main():
util.run_wsgi_app(app)
if __name__ == "__main__":
main()
<html>
<body>
<table>
{% for i in nums %}
<tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
{% endfor %}
</table>
</body>
</html>
import os
from google.appengine.ext.webapp import util
from flask import Flask
from mako.template import Template
app = Flask(__name__)
@app.route("/flaskmako/<numA>/<numB>")
def hello(numA,numB):
return Template(filename=os.path.join(
os.path.dirname(__file__),"flaskTestMakoTemplate.html")).render(numA=numA,numB=numB)
def main():
util.run_wsgi_app(app)
if __name__ == "__main__":
main()
<html>
<body>
<table>
% for i in range(int(numA)):
<tr><td>${i}</td><td>x ${numB}</td><td>= ${int(i) * int(numB)}</td></tr>
% endfor
</table>
</body>
</html>
import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from jinja2 import Environment, FileSystemLoader
class MainHandler(webapp.RequestHandler):
def get(self,numA,numB):
self.response.out.write(
Environment(loader=FileSystemLoader(
os.path.join(
os.path.dirname(__file__),"templates/"))).get_template(
"gjinjaTestTemplate.html").render(nums=range(int(numA)),numB=numB))
application = webapp.WSGIApplication(
[(r'/gjinja/(.*)/(.*)', MainHandler)],debug=False)
def main():
util.run_wsgi_app(application)
if __name__ == '__main__':
main()
<html>
<body>
<table>
{% for i in nums %}
<tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
{% endfor %}
</table>
</body>
</html>
import os, bottle
from bottle import get
from google.appengine.ext.webapp import util
from jinja2 import Environment, FileSystemLoader
bottle.debug(False)
app = bottle.default_app()
@get('/bottlejinja/:numA/:numB')
def index(numA,numB):
return Environment(loader=FileSystemLoader(
os.path.join(
os.path.dirname(__file__),"templates/"))).get_template(
"bottleJinjaTestTemplate.html").render(nums=range(int(numA)),numB=numB)
def main():
util.run_wsgi_app(app)
if __name__ == "__main__":
main()
<html>
<body>
<table>
{% for i in nums %}
<tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
{% endfor %}
</table>
</body>
</html>
import os
from google.appengine.ext.webapp import util
import web
from jinja2 import Environment, FileSystemLoader
web.config.debug = False
urls = (
'/webpyjinja/(.*)/(.*)', 'index'
)
app = web.application(urls, globals())
class index:
def GET(self,numA,numB):
return Environment(loader=FileSystemLoader(
os.path.join(
os.path.dirname(__file__),"templates/"))).get_template(
"webpyJinjaTestTemplate.html").render(nums=range(int(numA)),numB=numB)
def main():
util.run_wsgi_app(app.wsgifunc())
if __name__ == "__main__":
main()
<html>
<body>
<table>
{% for i in nums %}
<tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
{% endfor %}
</table>
</body>
</html>
import tipfy
from tipfy import RequestHandler, Rule
from tipfy.ext.jinja2 import render_response, default_config
default_config['templates_dir']='app/tipfy/templates'
class TipfyBenchmarkHandler(RequestHandler):
def get(self,numA,numB):
return render_response('tipfyJinjaTestTemplate.html', nums=range(numA), numB=numB)
app = tipfy.Tipfy(rules=[
Rule('/tipfy/<int:numA>/<int:numB>', endpoint='tipfy', handler=TipfyBenchmarkHandler)])
def main():
app.run()
if __name__ == '__main__':
main()
<html>
<body>
<table>
{% for i in nums %}
<tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
{% endfor %}
</table>
</body>
</html>
import os, tipfy
from tipfy import RequestHandler, Response, Rule
from mako.template import Template
class TipfyMakoBenchmarkHandler(RequestHandler):
def get(self,numA,numB):
return Response(
Template(
filename=os.path.join(
os.path.dirname(__file__),
"tipfyTestMakoTemplate.html")).render(numA=numA,numB=numB))
app = tipfy.Tipfy(rules=[
Rule('/tipfymako/<int:numA>/<int:numB>', endpoint='tipfy', handler=TipfyMakoBenchmarkHandler)])
def main():
app.run()
if __name__ == '__main__':
main()
<html>
<body>
<table>
% for i in range(int(numA)):
<tr><td>${i}</td><td>x ${numB}</td><td>= ${int(i) * int(numB)}</td></tr>
% endfor
</table>
</body>
</html>
And here is the test update wrapped inside a new consolidated benchmark. Also note that I modified all handlers to take advantage of AppEngine caching through the main() function.
Overhead 10:
tipfy w/Jinja: | 1000 total, 0 failed | 88.376s overall, 0.088s each | score: 1.000 |
flask w/Jinja: | 1000 total, 0 failed | 88.928s overall, 0.089s each | score: 1.006 |
webapp w/Mako: | 1000 total, 0 failed | 91.340s overall, 0.091s each | score: 1.034 |
tipfy w/Mako: | 1000 total, 0 failed | 91.624s overall, 0.092s each | score: 1.037 |
webapp w/Jinja: | 1000 total, 0 failed | 91.951s overall, 0.092s each | score: 1.040 |
bottle: | 1000 total, 0 failed | 98.956s overall, 0.099s each | score: 1.120 |
web.py: | 1000 total, 0 failed | 99.725s overall, 0.100s each | score: 1.128 |
webapp w/Django: | 1000 total, 0 failed | 100.619s overall, 0.101s each | score: 1.139 |
flask w/Mako: | 1000 total, 0 failed | 103.797s overall, 0.104s each | score: 1.175 |
web.py w/Jinja: | 1000 total, 0 failed | 103.985s overall, 0.104s each | score: 1.177 |
bottle w/Jinja: | 1000 total, 0 failed | 104.074s overall, 0.104s each | score: 1.178 |
bottle w/Mako: | 1000 total, 0 failed | 111.494s overall, 0.111s each | score: 1.262 |
web.py w/Mako: | 1000 total, 0 failed | 115.740s overall, 0.116s each | score: 1.310 |
Overhead 100:
tipfy w/Jinja: | 1000 total, 0 failed | 91.816s overall, 0.092s each | score: 1.000 |
webapp w/Mako: | 1000 total, 0 failed | 94.623s overall, 0.095s each | score: 1.031 |
bottle w/Mako: | 1000 total, 0 failed | 94.637s overall, 0.095s each | score: 1.031 |
flask w/Mako: | 1000 total, 0 failed | 94.700s overall, 0.095s each | score: 1.031 |
web.py w/Jinja: | 1000 total, 0 failed | 95.087s overall, 0.095s each | score: 1.036 |
web.py: | 1000 total, 0 failed | 103.446s overall, 0.103s each | score: 1.127 |
flask w/Jinja: | 1000 total, 0 failed | 104.981s overall, 0.105s each | score: 1.143 |
bottle: | 1000 total, 0 failed | 105.155s overall, 0.105s each | score: 1.145 |
webapp w/Django: | 1000 total, 0 failed | 107.200s overall, 0.107s each | score: 1.168 |
webapp w/Jinja: | 1000 total, 0 failed | 107.458s overall, 0.107s each | score: 1.170 |
bottle w/Jinja: | 1000 total, 0 failed | 107.681s overall, 0.108s each | score: 1.173 |
web.py w/Mako: | 1000 total, 0 failed | 119.635s overall, 0.120s each | score: 1.303 |
tipfy w/Mako: | 1000 total, 0 failed | 120.473s overall, 0.120s each | score: 1.312 |
Overhead 1000:
tipfy w/Jinja: | 1000 total, 0 failed | 89.339s overall, 0.089s each | score: 1.000 |
web.py: | 1000 total, 0 failed | 95.478s overall, 0.095s each | score: 1.069 |
bottle w/Jinja: | 1000 total, 0 failed | 95.611s overall, 0.096s each | score: 1.070 |
webapp w/Jinja: | 1000 total, 0 failed | 105.175s overall, 0.105s each | score: 1.177 |
web.py w/Jinja: | 1000 total, 0 failed | 106.210s overall, 0.106s each | score: 1.189 |
webapp w/Mako: | 1000 total, 0 failed | 106.407s overall, 0.106s each | score: 1.191 |
flask w/Mako: | 1000 total, 0 failed | 106.724s overall, 0.107s each | score: 1.195 |
tipfy w/Mako: | 1000 total, 0 failed | 107.081s overall, 0.107s each | score: 1.199 |
web.py w/Mako: | 1000 total, 0 failed | 107.688s overall, 0.108s each | score: 1.205 |
bottle: | 1000 total, 0 failed | 111.233s overall, 0.111s each | score: 1.245 |
bottle w/Mako: | 1000 total, 0 failed | 117.832s overall, 0.118s each | score: 1.319 |
flask w/Jinja: | 1000 total, 0 failed | 132.961s overall, 0.133s each | score: 1.488 |
webapp w/Django: | 1000 total, 0 failed | 138.364s overall, 0.138s each | score: 1.549 |
Overhead 10000:
tipfy w/Jinja: | 1000 total, 0 failed | 61.592s overall, 0.062s each | score: 1.000 |
tipfy w/Mako: | 1000 total, 0 failed | 75.728s overall, 0.076s each | score: 1.230 |
bottle w/Mako: | 1000 total, 0 failed | 81.219s overall, 0.081s each | score: 1.319 |
flask w/Mako: | 1000 total, 0 failed | 81.409s overall, 0.081s each | score: 1.322 |
web.py w/Mako: | 1000 total, 0 failed | 81.785s overall, 0.082s each | score: 1.328 |
webapp w/Mako: | 1000 total, 0 failed | 82.661s overall, 0.083s each | score: 1.342 |
webapp w/Jinja: | 1000 total, 0 failed | 84.205s overall, 0.084s each | score: 1.367 |
bottle w/Jinja: | 1000 total, 0 failed | 84.636s overall, 0.085s each | score: 1.374 |
web.py w/Jinja: | 1000 total, 0 failed | 85.994s overall, 0.086s each | score: 1.396 |
web.py: | 1000 total, 0 failed | 126.019s overall, 0.126s each | score: 2.046 |
flask w/Jinja: | 1000 total, 0 failed | 268.666s overall, 0.269s each | score: 4.362 |
bottle: | 1000 total, 0 failed | 315.039s overall, 0.315s each | score: 5.115 |
webapp w/Django: | 1000 total, 0 failed | 419.316s overall, 0.419s each | score: 6.808 |
Totals:
tipfy w/Jinja: | 331.123 seconds total | score: 4.000 |
webapp w/Mako: | 375.031 seconds total | score: 4.597 |
flask w/Mako: | 386.630 seconds total | score: 4.722 |
webapp w/Jinja: | 388.789 seconds total | score: 4.755 |
tipfy w/Mako: | 394.907 seconds total | score: 4.777 |
bottle w/Jinja: | 392.002 seconds total | score: 4.795 |
web.py w/Jinja: | 391.276 seconds total | score: 4.797 |
bottle w/Mako: | 405.181 seconds total | score: 4.930 |
web.py w/Mako: | 424.849 seconds total | score: 5.146 |
web.py: | 424.668 seconds total | score: 5.370 |
flask w/Jinja: | 595.536 seconds total | score: 8.000 |
bottle: | 630.383 seconds total | score: 8.625 |
webapp w/Django: | 765.499 seconds total | score: 10.663 |
Take these results with a grain of salt… Especially since the difference margin is sow low. It’s in no way a full featured comparison!!!
UPDATE!! (2010-08-26) Tenjin and Brevé are also added in the next benchmark