until nil {

AppEngine's Python templates Benchmark sequel3

26 Aug 2010

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:

webapp with Tenjin:

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>

webapp with Breve:

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)) ]
        ]
    ]
]

Bottle with Tenjin

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>

Bottle with Breve:

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)) ]
        ]
    ]
]

flask with Tenjin:

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>

flask with Breve:

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)) ]
        ]
    ]
]

tipfy with Tenjin:

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>

tipfy with Breve:

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)) ]
        ]
    ]
]

web.py with Tenjin:

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>

web.py with Breve:

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)) ]
        ]
    ]
]

And here is where the benchmarking happens:

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.

blog comments powered by Disqus

}

Older Posts... Blog powered by Jekyll.
Built using Liquid, RedCloth, Pygments and Blueprint.

Copyright © 2008-2010 Louis-Philippe Perron