JIT Compiler in PHP 8
PHP is an interpreted programming language, which has long struggled to improve its performance, which was particularly bleak in comparison to other competing programming languages used in web development. Among what is new in PHP 8, the JIT compiler deserves special attention, as it was officially included in the latest version. Will this step be an actual breakthrough, or nothing but a blip, an interesting addition to the language? Let's see what real benefits it can bring and whether it will bring any real-world improvement of the performance of your application.
Some readers might not remember that but starting with PHP 5.5 OPcache ran by Zend VM became an integral part of the language. It was a breakthrough in terms of improving application performance, and thus in the context of PHP web development services delivery. The later PHP 7 release brought even better performance. Even at that stage, there were proposals to implement and add a JIT compiler to the language; however, the community made every attempt to maximise efficiency using the available mechanisms.
What is JIT?
In simple words, JIT (just-in-time compilation) is a technique that enables compiling a program into machine code immediately before it is executed. In reality, this approach results in significantly faster code execution compared to a traditional interpreter. Sometimes JIT is referred to as a golden mean between flexibility in developing the application code and its runtime performance.
As I mentioned earlier, to date better PHP performance was based on OPCache using the so-called OPCode, which is a pre-compiled snippet of code in the form of commands given to the processor for execution, created after the script is run for the first time. The cached code can be run almost immediately by a virtual machine; however, it is still not native machine code.
JIT offers actual machine code that has been implemented in such a way as to work together with OPCache. When the script is first launched, if it’s been already cached in OPCache, it is immediately returned and compiled (if it was not already). However, in the case the script has not yet been cached, it first goes through the entire OPCode generation process, like previously. The whole process is perfectly illustrated in the diagram below.
If you think JIT is just part of the language / interpreter and it works automatically, then you are wrong. Unfortunately, it requires additional configuration, which at first glance does not seem particularly friendly and obvious. The whole operation is carried out in the php.ini configuration file, well known to PHP programmers.
The first important thing is that JIT only works when OPCache is enabled. Every default PHP installation has this value (opcache.enable) set to 1 immediately. Then, to unlock JIT, we need to set two parameters:
The first one (opcache.jit_buffer_size) is responsible for defining the amount of memory we want to allocate for code compilation. Setting an example value is therefore quite simple:
As for the second parameter (opcache.jit), it basically tells you how JIT is supposed to work. For a change, let me start with an example:
Yes, I was also surprised by the mysterious numerical value. At first I thought it was some kind of bitmask or something. However, when I analyzed the RFC, in which we find more details about the individual options, it turned out that each of these digits separately is a specific configuration value. So I would like to present three configuration values that may be the most used. They are a shortcut to help developers set the desired mode.
- opcache.jit = 1205 - all code is JIT compiled
- opcache.jit = 1235 - only selected code portions (based on their relative use) are passed to the JIT compilation
- opcache.jit = 1255 - application code is tracked for compilation by JIT and selected parts of the code are transferred to the compiler
Of course, I was not the only one who pointed out the questionable accessibility of such a configuration. Therefore, corrections have been added to the aforementioned RFC, in fact two aliases tracing and function that can be used in place of numeric values, e.g.
The difference between these modes is that JIT optimizes code only within the scope of one function when using the value function. However, when using a tracing value, it looks at the entire stack trace and looks for optimizable code. Of these two options, the most recommended is JIT tracing, which gives the most promising results in benchmarks, significantly increasing application performance.
Impact on web application performance
Every good developer should know that the most important factor that affects the application’s performance is the quality of their code. Another equally important factor is the choice of the technologies that make up its entire stack and how they are developed. In the case of PHP, OPCache was a revolution; however, JIT – despite the huge media hype – does not seem to be the same milestone, especially for web applications. Why?
JIT was introduced with the aim of compiling batches of code that are not subject to significant fluctuations. It detects snippets of code that are executed more than once and compiles them accordingly. As you may probably already guess, executing code while handling a single query/request of a web application depends on too many variables and in reality, it turns out that these identical snippets of code are few and far in between. What's more, in some cases there may be so few of them that instead of really speeding up the application, JIT will actually slow it down due to the additional burden of compiling the code.
In one of the publicly available tests of the project based on the Laravel framework and described in the medium.com article we can find out that running it in PHP 8 with JIT gives only a slight performance boost:
PHP 7.3: 131.37 req/s PHP 8.0 + JIT: 133.57 req/s
One can clearly see that in web applications the added performance will be barely noticeable.
This thesis is confirmed in the public benchmark presented by the PHP Group as part of the PHP 8 release.
As you can see, applications designed for web applications such as WordPress, MediaWiki or Symfony demo obtain results similar, slightly higher or even lower (when using Function JIT), than when running in PHP 8 without using JIT. In other cases, however, the situation is quite different. For synthetic benchmarks, or for tasks such as fractal generation, the efficiency can be up to 3 times higher.
In other cases, such as long-running applications, it is a gain of 1.5 - 2 times on performance. As you can see, this is actually a breakthrough that gives new possibilities in the use of the language outside of typical web applications.
What’s its use then?
The introduction of a JIT compiler in PHP is actually a step towards opening the language to new possibilities. JIT significantly improves code execution performance in CPU-intensive applications. This is about using PHP for completely new purposes, such as machine learning, complex mathematical calculations, or 2D/3D image processing/modelling.
A proof of concept video created by one of the leading PHP developers (Zeev Suraski) has been circulating on the web for quite a while now, showing performance improvements thanks to JIT – in his case, it was about generating fractals in real-time.
According to various benchmarks available on the web (for example https://www.phoronix.com/scan.php?page=article&item=php8-jit-june&num=2), depending on the application, JIT offers an actual, significant performance boost, ranging from single-digit improvements to nearly a hundred per cent.
Pros and cons
The implementation of the JIT compiler is a step towards opening the language to new possibilities and making, for example, PHP perfect for startups. The ability to directly compile PHP code will lower the dependence on C, which will make it easier and actually possible to develop without any particular performance implications. Undeniably, JIT improves application performance to a greater or lesser extent.
On the other hand, in its current shape, JIT is unable to significantly improve the performance of web applications, and in some specific scenarios, it might even result in worse performance than before. The way it works can also affect the development process, making it difficult to debug code, as there are some known issues with the xDebug tool. JIT will also require additional configuration know-how from developers.
At Droptica, where we provide Drupal services, Drupal support and our team includes many PHP programmers, we know that although JIT in the world is not entirely new (Facebook and their HHVM were the forerunners), it will soon become native part of the language with the release of PHP 8. JIT is going to improve code execution performance and expand the possibilities of using the language itself. I think that in the coming years we should keep an eye on its development, which can bring even better results, especially in the context of web applications.