A lot of the information below is out of date. Please see the new framework shootout page for the latest benchmarks.
Recently I went on a benchmarking spree and decided to throw ApacheBench at a bunch of the different web development technology platforms I interact with on a day-to-day basis. The results were interesting enough to me that I decided I'd take a post to share them here.
Disclaimer: The following test results should be taken with a *massive* grain of salt. If you know anything about benchmarking, you will know that the slightest adjustments have the potential to change things drastically. While I have tried to perform each test as fairly and accurately as possible, it would be foolish to consider these results as scientific in any way. It should also be noted that my goal here was not to see how fast each technology performs at its most optimized configuration, but rather what a minimal out-of-the-box experience looks like.
Test platform info:
- The hardware was an Intel Core2Quad Q9300, 2.5Ghz, 6MB Cache, 1333FSB, 2GB DDR RAM.
- The OS was CentOS v5.3 32-bit with a standard Apache Webserver setup.
- ApacheBench was used with only the -n and -c flags (1000 requests for the PHP frameworks, 5000 requests for everything else).
- Each ApacheBench test was run 5-10 times, with the "optimum average" chosen as the numbers represented here.
- The PHP tests were done using the standard Apache PHP module.
- The mod_wsgi tests were done in daemon mode set to 2 processes/15 threads.
- The SQL tests were done with mysqli ($mysql->query()) on PHP, and SQLAlchemy (conn.execute()) on Python fetching and printing 5 rows of data from a sample database.
We will start with the raw Apache benchmark.
For this test, Apache loaded a simple HTML file with random text:
Document Length: 6537 bytes Requests per second: 8356.23 [#/sec] (mean)
As expected, Apache is lightning fast.
Ok, so now that we've set the high water mark, let's take a look at some popular web technology platforms...
For this test, I created a PHP file that simply printed the $_SERVER array and some random text:
Document Length: 6135 bytes Requests per second: 2812.22 [#/sec] (mean)
Next, I decided to add some mysqli() code to connect to a database and print out a few rows of data:
Document Length: 283 bytes Requests per second: 2135.46 [#/sec] (mean)
Not at all bad, but who really wants to build a webapp using plain old PHP anymore?
Next I decided to test the two PHP frameworks that I am most familiar with.
For this test, I did a basic CakePHP install, and added a route that printed "hello world" from a template:
Document Length: 944 bytes Requests per second: 42.00 [#/sec] (mean)
As you can see, frameworks can be a blessing and a curse. On a side note, CakePHP was the only test that gave me failed requests.
For this test, I did a basic install and loaded Kohana's default welcome/index setup (which seemed lightweight enough to me to qualify as the test focal point):
Document Length: 1987 bytes Requests per second: 100.11 [#/sec] (mean)
Kohana fared much better than CakePHP, but it appears as though PHP's speed breaks down quickly once you start building a framework around it.
Unlike PHP, Python wasn't specifically created for the web. However, several years ago the WSGI spec was introduced which makes building web applications with Python a breeze. There are many different ways to depoly a WSGI app, but my tests were limited to Python's built-in WSGIServer, and Apache's mod_wsgi.
For this test, I used WSGI.org's test.wsgi:
Document Length: 4123 bytes Requests per second: 1658.35 [#/sec] (mean)
These results were quite impressive to me, considering that this is pure Python at work.
mod_wsgi is Apache's module to process WSGI scripts. This test used the exact same script as the previous WSGIServer example:
Document Length: 9552 bytes Requests per second: 3994.30 [#/sec] (mean)
Don't look now, but those results are better than the vanilla PHP test!
Next, I added SQLAlchemy imports to the test file and did a conn.execute(), in an attempt to try to mimic the PHP mysqli() test:
Document Length: 10212 bytes Requests per second: 3379.87 [#/sec] (mean)
I expected to lose quite a bit of speed with the introduction of an ORM, but it looks like SQLAlchemy is quite fast. These results were still over 1,000reqs/s more than the PHP example.
There's been a lot of buzz lately about the new TornadoServer, so I thought I'd throw it in the mix. This test uses their basic "hello world" example:
Document Length: 12 bytes Requests per second: 3330.00 [#/sec] (mean)
Nothing shocking here at first glance, but the Tornado is built to handle extreme loads. So, I increased the concurrency to 1,000 and threw 10,000 requests at it:
Document Length: 12 bytes Concurrency Level: 1000 Complete requests: 10000 Requests per second: 3288.15 [#/sec] (mean)
Almost exactly the same results! On a side note, none of the other tests subjects were able to handle a concurrency of 1,000.
TurboGears v2.0.3 (mod_wsgi)
TurboGears has quickly become my framework of choice, so naturally it was the first Python framework I tested.
For this test I setup a default quickstart project, and then added a hello() controller method which simply returned a "hello world" string:
Document Length: 11 bytes Requests per second: 470.18 [#/sec] (mean)
Next, I added a simple Genshi template with 1 level of inheritance:
Document Length: 1351 bytes Requests per second: 143.26 [#/sec] (mean)
While those results are not terrible, they should encourage you to check out the other templating options.
Next, I performed the same test with Mako:
Document Length: 1534 bytes Requests per second: 394.41 [#/sec] (mean)
As you can see, Mako is significantly faster than Genshi.
TurboGears also supports Jinja2, so I thought I'd also see how it held up:
Document Length: 1020 bytes Requests per second: 401.04 [#/sec] (mean)
I was not expecting Jinja2 to be faster than Mako, so I am not entirely sure about these results. However, the difference is only slight, and might have something to do with the fact that the dotted template names finder is disabled for Jinja templates.
Pylons v0.9.7 (mod_wsgi)
Mark Ramm (the current development lead of TurboGears) has been quoted as saying "TurboGears is to Pylons as Ubuntu is to Debian." With that in mind, I naturally wanted to see how much speed I was losing with TurboGears' "additions".
For this test I tried to mimic the first TurboGears test (basic layout, returning a string):
Document Length: 11 bytes Requests per second: 1961.00 [#/sec] (mean)
While I was expecting a gain from TurboGears' omissions, I wasn't quite expecting over four times the increase. I can only assume that the bare Pylons setup is pretty minimal, while TurboGears bootstraps a lot of stuff on for you by default.
Duplicating the Mako test in Pylons was also quite fast:
Document Length: 9743 bytes Requests per second: 1183.39 [#/sec] (mean)
Bottle v0.5.7 (mod_wsgi)
Bottle caught my attention recently as a minimalistic Python web framework, so I thought I'd put it through the grind as well.
For this test, I used their simple "hello world" example:
Document Length: 12 bytes Requests per second: 3912.43 [#/sec] (mean)
As you can see, Bottle is very close to bare WSGI as far as speed goes. On a side note, adding a sample template using Bottle's default templating package didn't seem to change these numbers at all.
Next I tested Bottle with its templating system switched to Mako:
Document Length: 41 bytes Requests per second: 3134.61 [#/sec] (mean)
Then I added a SQLAlchemy and performed a query:
Document Length: 9374 bytes Requests per second: 2503.27 [#/sec] (mean)
What was exciting to me about this test was that I found myself looking at a minimal Python framework equipped with a full templating system and ORM which all performed faster than the PHP+mysqli() test.
Django v1.1 (mod_wsgi)
I don't use Django, but I thought it would be worth including in this report. This is a basic Django setup printing out a simple hello world template:
Document Length: 1471 bytes Requests per second: 564.41 [#/sec] (mean)
As expected, a minimal Django setup is quite fast. (Note: This test included a SQLite database query.)
Ruby On Rails v2.2.3 (mod_passenger)
Document Length: 1291 bytes Requests per second: 433.57 [#/sec] (mean)
Note that the test app used here is doing a little bit more than just returning "Hello World".
As I said in the disclaimer, to arrive at any sort of decisive conclusions based on these numbers alone would be foolish. However, here is what all of this left me thinking:
- All of these technologies performed adequately for use with most projects.
- CakePHP was the only test that produced less than 100 requests per second. However, there are many articles available on optimizing CakePHP.
- These results made me wish I had moved away from PHP much earlier in my web development carrier. There are just better options these days when it comes to choosing a web development language (imho). If you must use PHP for a large project, I strongly suggest that you look into an accelerator.
- The Bottle+Mako+SQLAlchemy results struck me as an extremely minimal drop-in replacement for simple PHP sites.
- mod_wsgi is a huge winner in deploying Python webapps. Way to go Graham!
- Do your own bencharking, because your mileage **will* vary*!