FrankenPHP vs FPM: The Surprising Truth
45sThe title promises a surprising comparison, capturing curiosity about a trending tech topic.
▶ Play Clip"Delivers a thorough, honest benchmark comparison, but the title oversells the speed difference when the conclusion is 'no significant difference'."
This video discusses the performance comparison between PHP-FPM and FrankenPHP in classic mode, based on benchmarks conducted by the presenter's colleague Tim. It explains the difference between classic and worker modes, reviews previous benchmark claims, and presents new results showing that performance differences are minimal when properly benchmarked, with the main advantages of FrankenPHP lying in its unique features rather than raw speed.
The video addresses whether FrankenPHP in classic mode is faster than PHP-FPM, referencing a benchmark by colleague Tim.
Classic mode is a drop-in replacement for PHP-FPM or Apache mod_php, using multiple threads with shared-nothing architecture, meaning each request has its own memory scope and is torn down after the request.
Worker mode uses a worker script with an infinite loop to handle multiple requests, sharing memory and bootstrapping steps, making it faster but requiring custom implementation.
A blog post by Ivan claimed PHP-FPM was much worse than FrankenPHP classic mode, with half the latency and higher throughput, but the presenter suspected these results were too good to be true.
Tim benchmarked FrankenPHP classic mode vs PHP-FPM on a Hetzner server with 8 CPU cores, using default installations and low concurrency (8 or 16 processes) to avoid Linux scheduler effects.
Rendering 50KB of string data showed similar performance: ~7,000 req/s for FPM and ~6,930 for FrankenPHP, with negligible latency differences.
Adding a PDF header caused FrankenPHP performance to drop, while FPM improved, despite identical output, indicating web server compression differences.
Rendering random data flipped results: FrankenPHP handled 5,600 req/s vs FPM's 1,940, with half the latency, due to Caddy's smarter compression handling.
A simple hello world test showed both runtimes at ~18,000 req/s with similar response times (~0.9ms), confirming no significant difference.
Increasing concurrency to 100 did not significantly improve throughput; FrankenPHP had slightly higher req/s but worse 99th percentile latency (23ms vs 9ms).
Optimizing configurations for both runtimes yielded no significant changes, reinforcing that performance is similar.
The performance differences are explained by how Caddy (FrankenPHP) and Nginx (PHP-FPM) handle gzip compression. Caddy stops compressing random data, while Nginx wastes time compressing it.
Disabling compression (accept-encoding: identity) for HTML made results comparable, with FPM slightly faster, confirming no inherent runtime speed advantage.
FrankenPHP classic mode is not significantly faster than PHP-FPM; the real benefits are unique features like HTTP 103 early hints, Go extensions, and gRPC support. Migrating without using these features may not be worth it.
FrankenPHP classic mode offers no significant performance advantage over PHP-FPM when benchmarked fairly; the perceived differences stem from web server compression behaviors. The true value of FrankenPHP lies in its advanced features, not raw speed.
What is the shared-nothing architecture in FrankenPHP classic mode?
Each request has its own memory scope, and all data is torn down at the end of the request, preventing requests from interacting.
01:26
How does worker mode in FrankenPHP achieve faster performance?
It uses a worker script with an infinite loop to handle multiple requests, sharing memory and bootstrapping steps between requests.
02:48
What was the key finding of Tim's benchmark regarding PHP-FPM vs FrankenPHP classic mode?
There is no significant performance difference; both runtimes perform similarly when benchmarked fairly.
18:35
Why did the PDF content type test show a performance drop for FrankenPHP?
The difference was due to web server compression handling; Caddy and Nginx handle gzip compression differently depending on content type.
16:00
What is the recommended approach when considering migration to FrankenPHP?
Only migrate if you plan to use unique features like HTTP 103 early hints, Go extensions, or gRPC support; otherwise, the effort may not be worth it.
18:35
Shared-nothing architecture
Explains the fundamental design of classic mode, which ensures safety but limits performance gains.
01:26Previous benchmark claims
Highlights the discrepancy between earlier claims and the presenter's skepticism, setting up the investigation.
05:24Compression as the culprit
Reveals that web server compression, not PHP runtime, caused the observed performance differences.
16:00No significant difference
Provides a clear, evidence-based conclusion that challenges popular claims of FrankenPHP's superiority.
18:35[00:02] be faster than traditional PHP FPM setup. But not all applications can just migrate to this approach. But is Franken PHP in classic mode also faster than PHP
[00:15] FPM? This is what my colleague Tim set out to benchmark and in this video I'm going to discuss his findings. Mo, I'm Benjamin and my work is focused on PHP performance related topics for the last 10 years, helping thousands of
[00:30] developers along the way. Franken PHP is everywhere lately, starting with the PHP Foundation supporting the project, several Reddit posts, their gRPC several Reddit posts, their gRPC support, PHP extensions and go, and
[00:45] their scary cute elephants arriving. But what is Franken PHP? And what does classic and worker mode mean? The Franken PHP documentation has two handy pages to differentiate them. So let's go through classic mode.
[01:01] So without additional configuration, classic mode um makes a seamless drop in replacement for PHP FPM or Apache with mod PHP. So what does that mean? Um
[01:13] mod PHP. So what does that mean? Um classic mode starts multiple threats classic mode starts multiple threats that can handle PHP requests in parallel that can handle PHP requests in parallel but similar to um Apache and PHP FBM
[01:26] those requests have their own memory scope. So they have a shared nothing scope. So they have a shared nothing architecture um principle in place. That means all that is calculated within a request gets uh torn down at the end of
[01:42] the request and then the next re uh request that is handled by the same request that is handled by the same thread starts it over and over again. This is a very safe way of operating PHP because um requests can't interact with
[01:55] because um requests can't interact with each other and uh from a theoretical point of view the performance between any kind of web server and PHP runtime
[02:08] should be the same when they operate in this mode. So um not only should it be a drop in replacement but also should be similar in performance just from
[02:20] thinking about um it yeah why why should it be any different what what what should be the magic that makes the one faster or uh than the other it's uh running PHP and there are shared nothing separated from
[02:36] each other and uh we would expect that they are very similar So um how does it differentiate between uh worker mode? So um worker mode as
[02:48] uh worker mode? So um worker mode as they specify um allows you to um handle requests much faster and the reason uh for this is the following. So how does Franken PHP worker mode differentiate? Let's take a look at the
[03:03] configuration. So um if you want to use the worker So um if you want to use the worker mode, you have to specify a specific environment variable and point it to a worker script. So what is a worker
[03:16] worker script. So what is a worker script? What does it do? Let's uh comb through the documentation a little. So for Symfony, there's a specific runtime. Um uh we want to skip that. Laravel has its own documentation how to use that. I
[03:30] think it's valuable to look at how you can implement franken PHP worker mode for custom applications. So a worker script for a custom PHP
[03:42] application and franken PHP looks like this. We have a handler and the handler is called when the request request is received and then super globals are available for this request.
[03:59] However, as you can see here, we have an infinite loop of requests infinite loop of requests that um should handle more than one request in a loop until the number of maximum requests is reached. So, this
[04:14] code here either runs indefinitely or up to a variable max requests. it that means this script will handle multiple PHP requests um one after the
[04:27] multiple PHP requests um one after the other and the web server for franken php is calling the same PHP script over and over again that also means that the over again that also means that the memory is shared um every request uh
[04:40] uses the memory from uh that was also available to the request before and the request can theoretically share the memory so you can use this to save the whole bootstrapping step. With this in mind, uh obviously worker mode would be
[04:55] faster because we can share the bootstrapping of an application between requests. But we are also getting an understanding that Franken PHP in understanding that Franken PHP in classic mode would be uh similar in
[05:07] performance than PHP FPM and should not be like significantly faster. Um so the question would be is it generally a good idea to consider a migration from PHP FBM towards Franken PHP from a performance perspective or um is there a
[05:24] more uh differentiated um approach necessary? This blog post from Ivan from a few months ago is called Franken PHP versus PHP FPM benchmarks surprises and one clear winner. And what he finds is that the
[05:41] winner. And what he finds is that the performance of PHP FPM is much worse than that of Franken PHP classic mode. So his numbers show that
[05:58] requests at only half of the latent average latency. and with a different tool um found that the requests um per second were much higher and the latency was much much better than that of PHP FBM. So this is
[06:16] not only a surprise but from what we know about the differences between PHP FPM and Franken PHP classic mode, it's also too good to be true. That is why my colleague Tim set out to benchmark uh Frank and PHP classic mode and PHP FPM
[06:31] and the explicit goal was to make sure that we are not testing any other part of the stack that is that uh the test is running through. So we want to make sure
[06:43] we are measuring PHP runtime performance. That means Franken PHP performance. That means Franken PHP versus PHP FPM runtimes. And we don't want to see effects of the web server of the Linux scheduleuler and uh of other
[06:59] parts of the system to affect this performance. Tim summarized this in testing if Franken PHP classic mode is faster and more scalable than PHP FPM. and you find the link to the post in the description.
[07:14] So what uh did he use for a benchmark what uh did he use for a benchmark setup? So u we used a Hetszna server. It setup? So u we used a Hetszna server. It has eight CPU cores and um
[07:28] the setup of Franken PHP and of PHP FPM is based on the default installation instructions of both packages. So Frank and PHP is installed using the recommended curlsh command and engine x and php fpm are installed using the
[07:44] regular dbian packages. So after the installation only a few changes to engineext configuration were made um to avoid port conflicts but otherwise the setup was the same and not much was uh configured.
[08:01] So specifically the benchmark uh we ran that uh or Tim ran that using the viga command um is not using a lot of command um is not using a lot of concurrency. So in Ivan's blog post he
[08:15] concurrency. So in Ivan's blog post he was using a lot of concurrency 100 um uh virtual CPUs how it's called um in parallel. However, that puts the test at risk of uh the Linux scheduleuler being the actual
[08:30] driver of performance differences and also the way maybe how go threading works versus PHP FPM. And the reason for this is that if you have a um benchmarking machine that has less than
[08:46] benchmarking machine that has less than the number of um um benchmarking worker processes, then that means uh because the script is not doing any IO operations. Um you cannot really run them 100 scripts in complete parallel if
[09:04] them 100 scripts in complete parallel if you only have say eight or 16 CPUs. you only have say eight or 16 CPUs. And that requires scheduling in Linux to take place. And this means that we are not actually testing the PHP runtime
[09:17] not actually testing the PHP runtime anymore. So in Tim's test um he is running only eight or 16 uh processes in par parallel um for the eight CPUs um to be utilized. Let's see what the results show. In his
[09:32] first case, um he has a case where he renders 50 kilobytes of string data. And renders 50 kilobytes of string data. And as we would expect, um we see that the as we would expect, um we see that the requests per second between PHP FPM and
[09:47] requests per second between PHP FPM and Franken PHP are not so different. Um FPM Franken PHP are not so different. Um FPM has around 7,000 and Franken PHP around 6,930. So it's not a very significant
[09:59] differences uh difference between them. And then if we look at the latency at the 99 percentile uh we can see that both of them are around 2 milliseconds
[10:11] and the difference really is neglectable between uh both of them. So just for HTML response rendering if we rely on the runtime their performance isn't really that different. So case closed
[10:27] can we do use any of the two? Um not really. Let's have some fun. Um, one thing that Tim changed for the second test is
[10:39] he only added a header for application PDF response. And what you can see now PDF response. And what you can see now is that the performance of Franken PHP dropped significantly. So, FPM got faster. it was able to respond with more
[10:56] requests and franken PHP with less requests at a higher average response time. So that's quite confusing because the output is exactly the same.
[11:09] the output is exactly the same. So Tim set out with a third example So Tim set out with a third example now not rendering 50x characters but instead um he rendered random data to
[11:22] instead um he rendered random data to the output um using u the text HTML content type. So now everything flips and it's not PHP FPM that is much faster
[11:34] but the performance of PHP FPVM actually drops quite a lot. uh only 1,940 requests which is almost four times less than the 5,600 that Franken PHP was able to handle. And also Franken PHP um
[11:52] responded at half the time. So that's a quite different flip for just like returning random data instead of always the same character.
[12:04] Um obviously uh using the random extension puts a lot more work on the process. So it is expected that both PHP FPM and Franken PHP would be slower. Um
[12:16] but the difference is not really explainable by um PHP runtime explainable by um PHP runtime differences.
[12:28] like a very very simple hello world. And if he um doing that the request per if he um doing that the request per second increased significantly second increased significantly and you can see that both FPM and
[12:43] Franken PHP in classic mode respond at nearly the same rate of 18,000 requests uh per second and the response time of both of them is
[12:55] and the response time of both of them is also quite um close to each other around uh 0.9 milliseconds or 0.87 milliseconds. It's not really a
[13:07] difference where you would consider choosing one over the other. So maybe the differences that Ian uh saw in his post were because he used a lot of
[13:22] were because he used a lot of concurrency and Frank and PHP for some concurrency and Frank and PHP for some unknown reason is able to handle this way better than PHP FPM is. So this next
[13:34] way better than PHP FPM is. So this next case that um Tim tested was the hello world example again but not using eight um parallel requests in the benchmark. Instead he increased the number to 100
[13:48] which is much higher than the eight available um CPUs on the testing machine. So what we can see here is that through concurrency and um this makes a
[14:00] lot of sense the number of requests per second doesn't actually increase that second doesn't actually increase that lot um by 100 times for example. The lot um by 100 times for example. The reason for this is that uh at eight
[14:12] parallel uh requests per second, it was the the server was already under complete load almost and um increasing concurrency will not make the
[14:24] server response faster with eight CPUs because it's already at the edge of what because it's already at the edge of what it's available to deliver. Um, so the requests per second increased roughly by 3,000 or 4,000. So Franken
[14:39] PHP is able to do a little bit more. However, the latency, the average latency of Franken PHP is actually much worse at the 99 percentile. It's 23 milliseconds versus 9 milliseconds for PHP FBN. Still, those two numbers are
[14:57] not like magnitudes apart from each other. So the difference is still just very small and uh given this very simple hello world example it doesn't really explain or it shows that both of them
[15:14] explain or it shows that both of them are very similar in performance. configuration uh at the beginning he now looked at to maybe optimizing the
[15:27] configuration for both Franken PHP and PHP FBM. um increased some values, changed some stuff, but found no changes worth mentioning uh compared to the results before. So this leaves us with the differences
[15:43] that we saw before. How can we um explain that the performance is so different because uh based on the content types and the reason for this is that um the benchmarking tool viga is using um gzip as an accepted encoding.
[16:00] However, depending on what content type you respond from PHP, the web server actually handles this differently, either gzipping it itself or not doing
[16:13] either gzipping it itself or not doing that. And uh Tim listed the different um that. And uh Tim listed the different um approaches from Franken PHP caddy and approaches from Franken PHP caddy and PHP uh FBM and engine X and you can see
[16:25] that uh in the different scenarios sometimes both of them are compressing the data. However, in the PDF case for or not um however there are cases where
[16:38] they work uh in different ways. So sometimes both of them are um gzipping and sometimes just one of them. And it seems that um Franken PHP um web server
[16:53] compression is just much better than that of engine X because um in the case that of engine X because um in the case of random data we saw that PHP FBM was much much slower. So random data we saw uh the request per
[17:08] second in PHP FBM drop ping significantly and um Franken PHP classic mode was three times higher throughput and also half the performance and the reason for this is that uh P engine X is just very
[17:24] this is that uh P engine X is just very bad at compressing random data. um caddy sees that the data is random and stops compressing and engine X just compresses this wasting a lot of time because you don't uh get any benefit from
[17:39] compressing random data. So Tim tested um the performance without web server compression taking place for the HTML case. um he set accept encoding to case. um he set accept encoding to identity and reran the original HTML uh
[17:54] benchmark and then the numbers suddenly are very comparable to the ones for the are very comparable to the ones for the um PDF uh binary content um response uh FPM has a slightly higher request per second and also a slightly better
[18:10] response average latency than Franken PHP. So Franken PHP classic mode is not significantly faster than PHP FPM and depending on the test scenarios and
[18:22] depending on the test scenarios and application one or the other is slightly application one or the other is slightly faster and there are artificial ways to use both of them incorrectly that will actually hurt performance. So
[18:35] benchmarking those two against each other um requires you to carefully set up the benchmarks in a way that the you are actually testing uh apples to apples
[18:48] and um that is a difficult thing to to look at. The biggest benefit in Franken look at. The biggest benefit in Franken PHP classic uh mode IC is using its PHP classic uh mode IC is using its unique features like one HTTP 103 early
[19:02] unique features like one HTTP 103 early hints um writing go extensions hints um writing go extensions using the Mac realtime support or the um gRPC server support that was just released last week. Migrating from PHP
[19:15] FBM to Franken PHP classic mode without utilizing any of those features might not be worth the uh effort for now. If you like to stay informed about PHP performance topics such as this, please subscribe to this channel on YouTube or
[19:30] our newsletter. The link is in the description. Bye.
⚡ Saved you 0h 19m reading this? Transcribe any YouTube video for free — no signup needed.