In my last article, I talked a bit about creating a DI container. Today, we are going to talk about PHP frameworks and performance.
The current situation
Thanks to PHP Benchmarks, we can have a rough idea of what is the current fastest PHP framework. Surprisingly, it's not Symfony or Laravel as you would expect (they are the most famous, after all), but it's Ubiquity.
There are a few things to keep in mind that might explain this:
- Benchmarks are not always very representative of actual apps performance
- PHP is rarely the bottleneck
- Most requested stuff should be cached anyway
In my small framework experiment, I've made some tests against Lumen. Keep in mind my framework is still work in progress and I haven't made any real performance checks so far :-)
Testing a realistic workload
We know that "hello world" is not very useful, except to establish some kind of baseline to understand the numbers better. I've run some tests on my local server using Apache Bench like this ab -n 1000 -c 100
:
The hello world
Here it is:
Item | Req/s |
Baseline | 2680 |
Lumen | 1015 |
Kaly | 1028 |
As you would expect, booting a framework, even if it's a micro-framework, take away some performances. However, it's not that bad, since it only cuts performance by 2.5.
As we can see, it's not very difficult to have performances similar to Lumen.
The realistic workload
But what about more realistic workloads?
Here is what I tested on a sample app described in this article:
- the request is routed
- a database call is made (no cache) using Eloquent
- a template is rendered (and cached) using Twig or Plates (Kaly) or Blade (Lumen)
- the response is served
I used Eloquent in my framework in order to make the comparison simple.
For the template rendering, I tried with Twig and Plates (no surprise, Plates is faster and Twig is the slowest).
Item | Req/s | Notes |
Lumen | 572 | 64 failed requests |
Kaly | 434 | Using Twig |
Kaly | 495 | Using plates |
Somehow, I got more requests with Lumen, but if you substract the failed requests, it's more or less the same. I didn't dig deep into the issue, but I suspect that the concurrent requests are facing some kind of locked process at some point in Lumen.
Still great results! About twice as slow as a plain hello world, but in the meantime, we rendered a template and made a database call.
Getting more performances
Let's face it, if you want more performance, the default paradigm of PHP is not very good since we boot everything for each request. Ideally, the boot process should be only done once, and that's when application servers like Swoole or RoadRunner are doing.
I've been keeping an eye on RoadRunner, mostly because it runs on Windows (and I'm using a Windows dev machine), so I thought I might give it a try...
Item | Req/s |
Baseline | 2332 |
Kaly | 124 |
Kaly Hello | 458 |
Kaly + RR | 947 |
Kaly Hello + RR | 1897 |
- These numbers are not using Apache Bench but another load testing tool which is why the base numbers are not the same.
Using RoadRunner, you get between 5 to 10 times more req/s.
It's very clear what is the real cost of booting a framework vs handling a request. Preloading might help here, but even with that I'm not sure it would make such a difference.
As such, it's really important to use a framework that supports PSR-7 responses (in order to use tools like RoadRunner) with a clear distinction between the "boot" and "handling request" stage if you are really looking for pure php performance.
Conclusions
Even if your PHP framework is not the only part of the equation (for example, when serving cached, static content, its impact is almost negligible), it might matter if you are serving a lot of dynamic content (in busy web apps where everything is linked to a session).
Final note: please take all these numbers with caution, I know you can probably tweak things here and there to get different values, it's the general idea that matters, not the fine details.