9/3/19

Tornado vs Starlette in 2019

For the last 10 years I have been using mainly  Tornado as my web framework of choice.  And I mostly use it synchronously.  Only when dealing with uploading files did i use some asynchronous tornado features.   Most of my apps have database backends.  With database involved, it's not worth it to convert my code to asynchronous mode.  Tornado has been worked well for me I have no intention of stopping using it in the near future.  But I do want to keep my eyes open for new tool that enables me to build fast app fast.

In the last few years, there are several new async web frameworks.  One of them is Starlette, I decided to evaluate it.  The first step is to compare it basic performance with Tornado.

Starlette, using uvicorn, claims to be one of the fastest python web framework.  I copied the codes directly from its website:
from starlette.responses import PlainTextResponse

async def app(scope, receive, send):
    assert scope['type'] == 'http'
    response = PlainTextResponse('Hello, world!')
    await response(scope, receive, send)
I run it with "uvicorn example:app", then measure rps using 'ab':
ab -n 1000 -c 10 127.0.0.1:8000/
 The result is on average 4200 requests per second.

I then run basic Tornado app, again with code copied directly from tornado website:
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
 Using the same measurement, the ab result is a average 1900 requests/second.

So it looks like Starlette/uvicorn is more than 2 times faster than Tornado.   However, the code on Tornado website is actually not the best for production use.  Just replace "app.listen(8888)" the second to last line with following:
server = HTTPServer(app)
server.bind(8888)
server.start(0)
(and put "from tornado.httpserver import HTTPServer" at the beginning of the file),  its performance increases to 4700 requests/second, actually faster than Starlette.

The code change to Tornado make the app start 4 processes instead of 1.  Of course we can also do the similar for the Starlette app:
uvicorn --workers 4 example:app
The result now is 7500 requests/second, surpassing multi-process Tornado again.

These are superficial results that don't mean much.  But it did make me appreciate the works that got into Tornado that keep it performant in these years.

Result Summary:
  • Tornado (default): 1900 rps
  • Startlette/uvicorn (default): 4200 rps
  • Tornado (4 workers): 4700 rps
  • Starlette/uvicorn (4 workers): 7500 rps