So after the 2 last articles(1, 2) on Python’s templating engines, here comes the best part. I gave Tenjin and BrevĂ© a try. Tenjin has a reputation to be the fastest engine around and BrevĂ© is interesting being a corner case engine with a refreshing templating philosophy. Adding 2 more engines made the unsuspected happen: performance positions started to polarize, yielding clearer conclusions from the results than previous benchmarks.
Here are, for completeness sake, the new contenders implementations:
import os, tenjin, tenjin.gae
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from tenjin.helpers import *
tenjin.gae.init()
class MainHandler(webapp.RequestHandler):
def get(self,numA,numB):
context = { 'numA' : numA, 'numB' : numB}
self.response.out.write(tenjin.Engine(path=[
os.path.join(os.path.dirname(__file__),"templates")]
).render('gTenjinTestTemplate.pyhtml',context))
application = webapp.WSGIApplication(
[(r'/gtenjin/(.*)/(.*)', MainHandler)],debug=False)
def main():
util.run_wsgi_app(application)
if __name__ == '__main__':
main()
<html>
<body>
<table>
<?py for i in range(int(numA)): ?>
<?py rez = int(i) * int(numB) ?>
<tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
<?py #endfor ?>
</table>
</body>
</html>
import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from breve import Template
from breve.tags.html import tags
class MainHandler(webapp.RequestHandler):
def get(self,numA,numB):
bvars = dict ( numA = numA, numB = numB )
self.response.out.write(
Template(tags,root=os.path.dirname(__file__)
).render('gBreveTestTemplate',bvars))
application = webapp.WSGIApplication(
[(r'/gbreve/(.*)/(.*)', MainHandler)],debug=False)
def main():
util.run_wsgi_app(application)
if __name__ == '__main__':
main()
html [
body [
table [
[ tr [
td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
] for i in range(int(numA)) ]
]
]
]
import os, bottle, tenjin, tenjin.gae
from bottle import get
from google.appengine.ext.webapp import util
from tenjin.helpers import *
bottle.debug(False)
app = bottle.default_app()
tenjin.gae.init()
@get('/bottletenjin/:numA/:numB')
def index(numA,numB):
context = { 'numA' : numA, 'numB' : numB}
return tenjin.Engine(
path=[os.path.join(os.path.dirname(__file__),"templates")]
).render('bottleTenjinTestTemplate.pyhtml',context)
def main():
util.run_wsgi_app(app)
if __name__ == "__main__":
main()
<html>
<body>
<table>
<?py for i in range(int(numA)): ?>
<?py rez = int(i) * int(numB) ?>
<tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
<?py #endfor ?>
</table>
</body>
</html>
import os, bottle
from bottle import get
from google.appengine.ext.webapp import util
from breve import Template
from breve.tags.html import tags
bottle.debug(False)
app = bottle.default_app()
@get('/bottlebreve/:numA/:numB')
def index(numA,numB):
bvars = dict ( numA = numA, numB = numB )
return Template(tags,root=os.path.dirname(__file__)
).render('bottleBreveTestTemplate',bvars)
def main():
util.run_wsgi_app(app)
if __name__ == "__main__":
main()
html [
body [
table [
[ tr [
td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
] for i in range(int(numA)) ]
]
]
]
import os, tenjin, tenjin.gae
from google.appengine.ext.webapp import util
from flask import Flask
from tenjin.helpers import *
tenjin.gae.init()
app = Flask(__name__)
@app.route("/flasktenjin/<numA>/<numB>")
def test(numA,numB):
context = { 'numA' : numA, 'numB' : numB}
return tenjin.Engine(
path=[os.path.join(os.path.dirname(__file__),"templates")]
).render('flaskTenjinTestTemplate.pyhtml',context)
def main():
util.run_wsgi_app(app)
if __name__ == "__main__":
main()
<html>
<body>
<table>
<?py for i in range(int(numA)): ?>
<?py rez = int(i) * int(numB) ?>
<tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
<?py #endfor ?>
</table>
</body>
</html>
import os
from google.appengine.ext.webapp import util
from flask import Flask
from breve import Template
from breve.tags.html import tags
app = Flask(__name__)
@app.route("/flaskbreve/<numA>/<numB>")
def test(numA,numB):
bvars = dict ( numA = numA, numB = numB )
return Template(tags,root=os.path.dirname(__file__)
).render('flaskBreveTestTemplate',bvars)
def main():
util.run_wsgi_app(app)
if __name__ == "__main__":
main()
html [
body [
table [
[ tr [
td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
] for i in range(int(numA)) ]
]
]
]
import os, tipfy, tenjin, tenjin.gae
from tipfy import RequestHandler, Response, Rule
from tenjin.helpers import *
tenjin.gae.init()
class TipfyTenjinBenchmarkHandler(RequestHandler):
def get(self,numA,numB):
context = { 'numA' : numA, 'numB' : numB}
return Response(tenjin.Engine(
path=[os.path.join(os.path.dirname(__file__),"templates")]
).render('tipfyTenjinTestTemplate.pyhtml',context))
app = tipfy.Tipfy(rules=[
Rule('/tipfytenjin/<int:numA>/<int:numB>',
endpoint='tipfy',
handler=TipfyTenjinBenchmarkHandler)
])
def main():
app.run()
if __name__ == '__main__':
main()
<html>
<body>
<table>
<?py for i in range(int(numA)): ?>
<?py rez = int(i) * int(numB) ?>
<tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
<?py #endfor ?>
</table>
</body>
</html>
import os, tipfy
from tipfy import RequestHandler, Response, Rule
from breve import Template
from breve.tags.html import tags
class TipfyMakoBenchmarkHandler(RequestHandler):
def get(self,numA,numB):
bvars = dict ( numA = numA, numB = numB )
return Response(
Template(tags,root=os.path.dirname(__file__)
).render('tipfyBreveTestTemplate',bvars))
app = tipfy.Tipfy(rules=[
Rule('/tipfybreve/<int:numA>/<int:numB>',
endpoint='tipfy',
handler=TipfyMakoBenchmarkHandler)
])
def main():
app.run()
if __name__ == '__main__':
main()
html [
body [
table [
[ tr [
td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
] for i in range(int(numA)) ]
]
]
]
import os, web, tenjin, tenjin.gae
from google.appengine.ext.webapp import util
from tenjin.helpers import *
tenjin.gae.init()
web.config.debug = False
urls = (
'/webpytenjin/(.*)/(.*)', 'index'
)
app = web.application(urls, globals())
class index:
def GET(self,numA,numB):
context = { 'numA' : numA, 'numB' : numB}
return tenjin.Engine(path=[
os.path.join(os.path.dirname(__file__),"templates")]
).render('webpyTenjinTestTemplate.pyhtml',context)
def main():
util.run_wsgi_app(app.wsgifunc())
if __name__ == "__main__":
main()
<html>
<body>
<table>
<?py for i in range(int(numA)): ?>
<?py rez = int(i) * int(numB) ?>
<tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
<?py #endfor ?>
</table>
</body>
</html>
import os
from google.appengine.ext.webapp import util
import web
from breve import Template
from breve.tags.html import tags
web.config.debug = False
urls = (
'/webpybreve/(.*)/(.*)', 'index'
)
app = web.application(urls, globals())
class index:
def GET(self,numA,numB):
bvars = dict ( numA = numA, numB = numB )
return Template(tags,root=os.path.dirname(__file__)
).render('webpyBreveTestTemplate',bvars)
def main():
util.run_wsgi_app(app.wsgifunc())
if __name__ == "__main__":
main()
html [
body [
table [
[ tr [
td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
] for i in range(int(numA)) ]
]
]
]
Overhead 10:
webapp w/Tenjin: | 250 total, 0 failed | 7.018s overall, 0.028s each | score: 1.000 |
web.py: | 250 total, 0 failed | 7.062s overall, 0.028s each | score: 1.006 |
flask w/Tenjin: | 250 total, 0 failed | 7.072s overall, 0.028s each | score: 1.008 |
tipfy w/Jinja: | 250 total, 0 failed | 7.074s overall, 0.028s each | score: 1.008 |
tipfy w/Tenjin: | 250 total, 0 failed | 7.088s overall, 0.028s each | score: 1.010 |
bottle: | 250 total, 0 failed | 7.102s overall, 0.028s each | score: 1.012 |
web.py w/Tenjin: | 250 total, 0 failed | 7.104s overall, 0.028s each | score: 1.012 |
bottle w/Tenjin: | 250 total, 0 failed | 7.138s overall, 0.029s each | score: 1.017 |
webapp w/Django: | 250 total, 0 failed | 7.162s overall, 0.029s each | score: 1.020 |
flask w/Jinja: | 250 total, 0 failed | 7.207s overall, 0.029s each | score: 1.027 |
flask w/Breve: | 250 total, 0 failed | 7.342s overall, 0.029s each | score: 1.046 |
web.py w/Breve: | 250 total, 0 failed | 7.352s overall, 0.029s each | score: 1.048 |
bottle w/Breve: | 250 total, 0 failed | 7.416s overall, 0.030s each | score: 1.057 |
webapp w/Breve: | 250 total, 0 failed | 7.616s overall, 0.030s each | score: 1.085 |
tipfy w/Breve: | 250 total, 0 failed | 7.632s overall, 0.031s each | score: 1.087 |
webapp w/Mako: | 250 total, 0 failed | 7.849s overall, 0.031s each | score: 1.118 |
webapp w/Jinja: | 250 total, 0 failed | 7.937s overall, 0.032s each | score: 1.131 |
web.py w/Jinja: | 250 total, 0 failed | 8.011s overall, 0.032s each | score: 1.141 |
bottle w/Jinja: | 250 total, 0 failed | 8.064s overall, 0.032s each | score: 1.149 |
bottle w/Mako: | 250 total, 0 failed | 8.252s overall, 0.033s each | score: 1.176 |
flask w/Mako: | 250 total, 0 failed | 8.253s overall, 0.033s each | score: 1.176 |
web.py w/Mako: | 250 total, 0 failed | 8.277s overall, 0.033s each | score: 1.179 |
tipfy w/Mako: | 250 total, 0 failed | 8.320s overall, 0.033s each | score: 1.185 |
Overhead 100:
tipfy w/Jinja: | 250 total, 0 failed | 7.059s overall, 0.028s each | score: 1.000 |
web.py: | 250 total, 0 failed | 7.250s overall, 0.029s each | score: 1.027 |
bottle w/Tenjin: | 250 total, 0 failed | 7.366s overall, 0.029s each | score: 1.044 |
web.py w/Tenjin: | 250 total, 0 failed | 7.384s overall, 0.030s each | score: 1.046 |
webapp w/Tenjin: | 250 total, 0 failed | 7.521s overall, 0.030s each | score: 1.065 |
flask w/Jinja: | 250 total, 0 failed | 7.635s overall, 0.031s each | score: 1.082 |
flask w/Tenjin: | 250 total, 0 failed | 7.692s overall, 0.031s each | score: 1.090 |
bottle: | 250 total, 0 failed | 7.702s overall, 0.031s each | score: 1.091 |
tipfy w/Tenjin: | 250 total, 0 failed | 7.760s overall, 0.031s each | score: 1.099 |
webapp w/Mako: | 250 total, 0 failed | 7.984s overall, 0.032s each | score: 1.131 |
web.py w/Mako: | 250 total, 0 failed | 8.005s overall, 0.032s each | score: 1.134 |
bottle w/Mako: | 250 total, 0 failed | 8.068s overall, 0.032s each | score: 1.143 |
web.py w/Jinja: | 250 total, 0 failed | 8.079s overall, 0.032s each | score: 1.145 |
bottle w/Jinja: | 250 total, 0 failed | 8.129s overall, 0.033s each | score: 1.152 |
webapp w/Jinja: | 250 total, 0 failed | 8.260s overall, 0.033s each | score: 1.170 |
flask w/Mako: | 250 total, 0 failed | 8.279s overall, 0.033s each | score: 1.173 |
webapp w/Django: | 250 total, 0 failed | 8.316s overall, 0.033s each | score: 1.178 |
tipfy w/Mako: | 250 total, 0 failed | 8.419s overall, 0.034s each | score: 1.193 |
flask w/Breve: | 250 total, 0 failed | 9.245s overall, 0.037s each | score: 1.310 |
tipfy w/Breve: | 250 total, 0 failed | 9.310s overall, 0.037s each | score: 1.319 |
bottle w/Breve: | 250 total, 0 failed | 9.311s overall, 0.037s each | score: 1.319 |
web.py w/Breve: | 250 total, 0 failed | 9.314s overall, 0.037s each | score: 1.320 |
webapp w/Breve: | 250 total, 0 failed | 9.356s overall, 0.037s each | score: 1.325 |
Overhead 1000:
tipfy w/Jinja: | 250 total, 0 failed | 7.900s overall, 0.032s each | score: 1.000 |
tipfy w/Mako: | 250 total, 0 failed | 9.061s overall, 0.036s each | score: 1.147 |
flask w/Mako: | 250 total, 0 failed | 9.100s overall, 0.036s each | score: 1.152 |
web.py w/Mako: | 250 total, 0 failed | 9.109s overall, 0.036s each | score: 1.153 |
bottle w/Mako: | 250 total, 0 failed | 9.136s overall, 0.037s each | score: 1.156 |
webapp w/Mako: | 250 total, 0 failed | 9.144s overall, 0.037s each | score: 1.158 |
webapp w/Jinja: | 250 total, 0 failed | 9.268s overall, 0.037s each | score: 1.173 |
web.py w/Jinja: | 250 total, 0 failed | 9.355s overall, 0.037s each | score: 1.184 |
web.py: | 250 total, 0 failed | 9.359s overall, 0.037s each | score: 1.185 |
bottle w/Jinja: | 250 total, 0 failed | 9.364s overall, 0.037s each | score: 1.185 |
webapp w/Tenjin: | 250 total, 0 failed | 9.688s overall, 0.039s each | score: 1.226 |
bottle w/Tenjin: | 250 total, 0 failed | 9.746s overall, 0.039s each | score: 1.234 |
tipfy w/Tenjin: | 250 total, 0 failed | 9.899s overall, 0.040s each | score: 1.253 |
web.py w/Tenjin: | 250 total, 0 failed | 10.525s overall, 0.042s each | score: 1.332 |
flask w/Tenjin: | 250 total, 0 failed | 10.742s overall, 0.043s each | score: 1.360 |
flask w/Jinja: | 250 total, 0 failed | 13.494s overall, 0.054s each | score: 1.708 |
bottle: | 250 total, 0 failed | 13.561s overall, 0.054s each | score: 1.717 |
webapp w/Django: | 250 total, 0 failed | 16.773s overall, 0.067s each | score: 2.123 |
flask w/Breve: | 250 total, 0 failed | 31.060s overall, 0.124s each | score: 3.932 |
webapp w/Breve: | 250 total, 0 failed | 31.300s overall, 0.125s each | score: 3.962 |
web.py w/Breve: | 250 total, 0 failed | 31.341s overall, 0.125s each | score: 3.967 |
tipfy w/Breve: | 250 total, 0 failed | 31.420s overall, 0.126s each | score: 3.977 |
bottle w/Breve: | 250 total, 0 failed | 31.428s overall, 0.126s each | score: 3.978 |
Overhead 10000:
tipfy w/Jinja: | 250 total, 0 failed | 16.181s overall, 0.065s each | score: 1.000 |
tipfy w/Mako: | 250 total, 0 failed | 18.838s overall, 0.075s each | score: 1.164 |
web.py w/Mako: | 250 total, 0 failed | 20.317s overall, 0.081s each | score: 1.256 |
flask w/Mako: | 250 total, 0 failed | 20.408s overall, 0.082s each | score: 1.261 |
bottle w/Mako: | 250 total, 0 failed | 20.704s overall, 0.083s each | score: 1.280 |
webapp w/Mako: | 250 total, 0 failed | 21.314s overall, 0.085s each | score: 1.317 |
webapp w/Jinja: | 250 total, 0 failed | 21.567s overall, 0.086s each | score: 1.333 |
bottle w/Jinja: | 250 total, 0 failed | 21.729s overall, 0.087s each | score: 1.343 |
web.py w/Jinja: | 250 total, 0 failed | 22.290s overall, 0.089s each | score: 1.378 |
web.py: | 250 total, 0 failed | 30.913s overall, 0.124s each | score: 1.910 |
bottle w/Tenjin: | 250 total, 0 failed | 34.131s overall, 0.137s each | score: 2.109 |
tipfy w/Tenjin: | 250 total, 0 failed | 34.571s overall, 0.138s each | score: 2.137 |
webapp w/Tenjin: | 250 total, 0 failed | 35.038s overall, 0.140s each | score: 2.165 |
flask w/Tenjin: | 250 total, 0 failed | 41.125s overall, 0.164s each | score: 2.542 |
web.py w/Tenjin: | 250 total, 0 failed | 41.612s overall, 0.166s each | score: 2.572 |
flask w/Jinja: | 250 total, 0 failed | 67.461s overall, 0.270s each | score: 4.169 |
bottle: | 250 total, 0 failed | 72.529s overall, 0.290s each | score: 4.482 |
webapp w/Django: | 250 total, 0 failed | 104.344s overall, 0.417s each | score: 6.448 |
bottle w/Breve: | 250 total, 0 failed | 254.673s overall, 1.019s each | score: 15.739 |
web.py w/Breve: | 250 total, 0 failed | 254.768s overall, 1.019s each | score: 15.745 |
flask w/Breve: | 250 total, 0 failed | 255.902s overall, 1.024s each | score: 15.815 |
webapp w/Breve: | 250 total, 0 failed | 256.810s overall, 1.027s each | score: 15.871 |
tipfy w/Breve: | 250 total, 0 failed | 259.073s overall, 1.036s each | score: 16.011 |
Totals:
tipfy w/Jinja: | 38.214 seconds total | score: 4.008 |
tipfy w/Mako: | 44.638 seconds total | score: 4.689 |
web.py w/Mako: | 45.708 seconds total | score: 4.722 |
webapp w/Mako: | 46.292 seconds total | score: 4.724 |
bottle w/Mako: | 46.161 seconds total | score: 4.755 |
flask w/Mako: | 46.040 seconds total | score: 4.762 |
webapp w/Jinja: | 47.032 seconds total | score: 4.807 |
bottle w/Jinja: | 47.286 seconds total | score: 4.829 |
web.py w/Jinja: | 47.735 seconds total | score: 4.848 |
web.py: | 54.585 seconds total | score: 5.129 |
bottle w/Tenjin: | 58.381 seconds total | score: 5.404 |
webapp w/Tenjin: | 59.265 seconds total | score: 5.457 |
tipfy w/Tenjin: | 59.318 seconds total | score: 5.499 |
web.py w/Tenjin: | 66.625 seconds total | score: 5.962 |
flask w/Tenjin: | 66.630 seconds total | score: 5.998 |
flask w/Jinja: | 95.796 seconds total | score: 7.986 |
bottle: | 100.894 seconds total | score: 8.302 |
webapp w/Django: | 136.596 seconds total | score: 10.770 |
web.py w/Breve: | 302.775 seconds total | score: 22.079 |
bottle w/Breve: | 302.829 seconds total | score: 22.093 |
flask w/Breve: | 303.550 seconds total | score: 22.102 |
webapp w/Breve: | 305.081 seconds total | score: 22.243 |
tipfy w/Breve: | 307.435 seconds total | score: 22.394 |
Thats all for today! Stay tuned for tomorrow’s conclusion.