Creating the fastest PHP framework

Creating the fastest PHP framework

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:

ItemReq/s
Baseline2680
Lumen1015
Kaly1028

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

ItemReq/sNotes
Lumen57264 failed requests
Kaly434Using Twig
Kaly495Using 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...

ItemReq/s
Baseline2332
Kaly124
Kaly Hello458
Kaly + RR947
Kaly Hello + RR1897
  • 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.