PHP opcode – Improve application performance without changing your code

Valerio - Nov 8 - - Dev Community

The PHP opcode generated by the PHP engine is strongly influenced by the way you write your code. Not only in terms of the number of statements to accomplish a task. Clearly it matters a lot, and I think it’s obvious to you.

What could be less obvious is that even the syntax of the code can completely change the generated opcode causing a lot of overhead for the machine’s CPU to execute the exact same code.

In the last few years my SaaS product has grown a lot, and it has given me the opportunity to go deeper and deeper into the optimization techniques to run my workload as efficiently as possible.

The results I saw are impressive and helped me a lot in unlocking free cash flow to continue developing my SaaS journey.

At this point the PHP process inside my SaaS product is processing more than 1.2 billion (with B) data packets every day on a machine with 2vCPU and 8GB of memory. I use an AWS autoscaling group to have more flexibility in case of unpredictable spikes, but it rarely adds a second machine (one/two times a week).

For more technical articles you can follow me on Linkedin or X.

Recently I also wrote about the migration of the Inspector servers to ARM instances: https://inspector.dev/inspector-adoption-of-graviton-arm-instances-and-what-results-weve-seen/

Let's go into the topic of the article. I think you will find it very interesting.

What is the PHP opcode

PHP opcode stands for operation code, and it refers to the low-level instructions that are executed by the PHP engine after the PHP source code you write has been compiled.

In PHP, code compilation happens at runtime, basically the first time that your code is taken by the PHP engine it will be compiled into this machine friendly code, cached, so the engine doesn’t compile the same code again, and then executed.

This is a simple representation of the process:

PHP opcode execution flow

PHP opcode caching

Caching the PHP opcode allows you to save three steps in the process of executing the code: Parsing the raw PHP code, Tokenization, and Compilation.

Once the opcode is generated for the first time it is stored in memory so it can be reused in subsequent requests. This reduces the need for the PHP engine to recompile the same PHP code every time it’s executed, saving a lot of CPU and memory consumption.

The most commonly used opcode cache in PHP is OPCache, and it is included by default since PHP 5.5 up to recent versions. It is highly efficient and widely supported.

Caching the precompiled script bytecode requires invalidating the cache after every deployment. Because if changed files have the bytecode version in the cache PHP will continue to run the old version of the code. Until you purge the opcode cache so the new code will be compiled again generating a new cache item.

How to investigate PHP opcode

To understand how different syntax can impact the script's opcode we need a way to grab the compiled code generated by the PHP engine.

There are two ways of getting the opcode.

OPCache native functions

If you have the OPCache extension enable on your machine you can use its native functions to get the opcode of a specific php file:

// Force compilation of a script
opcache_compile_file(__DIR__.'/yourscript.php');

// Get OPcache status
$status = opcache_get_status();

// Inspect the script's entry in the cache
print_r($status['scripts'][__DIR__.'/yourscript.php']);
Enter fullscreen mode Exit fullscreen mode

VLD (Vulcan Logic Disassembler) php extension

VLD is a popular PHP extension that disassembles compiled PHP code and outputs the opcode. It’s a powerful tool for understanding how PHP interprets and executes your code.Once installed you can run a PHP script with VLD enabled by using the php command with -d options:

php -d vld.active=1 -d vld.execute=0 yourscript.php
Enter fullscreen mode Exit fullscreen mode

The output will include detailed information about the compiled opcode, including each operation, its associated line of code, and more.

Use 3v4l (Acronym for EVAL)

3v4l is a very useful online tool that allows you to view the opcode generated by a PHP code you type into the editor. It basically is a PHP server with VLD installed so it can grab the VLD output and show you the opcode into the browser.

As it's free distributed we’ll use this online tool for the next analyses.

How to generate efficient PHP opcode

3v4l is perfect to understand how the code syntax we use can influence the resulting PHP opcode in a good or bad way.Let’s start pasting the code below into 3v4l. Keep the configuration "all supported versions" and click on "eval".

<?php

namespace App;

strlen('ciao');
Enter fullscreen mode Exit fullscreen mode

After executing the code a tab menu will appear on the bottom. Navigate to the VLD tab to visualize the correspondent OPcode.

line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    5     0  E >   INIT_NS_FCALL_BY_NAME                                    'App%5CSpace%5Cstrlen'
          1        SEND_VAL_EX                                              'ciao'
          2        DO_FCALL                                      0          
          3      > RETURN                                                   1
Enter fullscreen mode Exit fullscreen mode

Note that the first operation is INIT_NS_FCALL_BY_NAME. The interpreter constructs the name of the function using the namespace of the current file. But it doesn’t exist in the App\Example namespace, so how does it work?

The interpreter will check if the function exists in the current namespace. If it doesn’t it tries to call the corresponding core function.

Here we have the opportunity to tell the interpreter to avoid this double check and directly execute the core function.

Try to add a backslash () before strlen and click "eval":

<?php

namespace App;

\strlen('ciao');
Enter fullscreen mode Exit fullscreen mode

In the VLD tab you can now see the opcode with just one statement.

line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    5     0  E > > RETURN                                                   1
Enter fullscreen mode Exit fullscreen mode

It's because you communicated the exact location of the function, so it doesn’t need to consider any fallback.

If don't like to use the the backslash, you can import the function like any other class from the root namespace:

<?php

namespace App;

use function strlen;

strlen('ciao');
Enter fullscreen mode Exit fullscreen mode

Leverage Automatic Opcode Optimizations

There are also a lot of internal automatisms of the PHP engine to generate an optimized opcode evaluating static expressions in advance. This was one of the most important reasons of the great performance improvement of PHP since the version 7.x

Being aware of these dynamics can really help you reduce resource consumption and cut costs. Once I made this research, I started using these tricks throughout the code.

Let me show you an example using PHP constants. Run this script into 3v4l:

<?php

namespace App;

if (PHP_OS === 'Linux') {
    echo "Linux";
}
Enter fullscreen mode Exit fullscreen mode

Take a look at the first two lines of the PHP opcode:

line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    5     0  E >   FETCH_CONSTANT                                   ~0      'App%5CPHP_OS'
          1        IS_IDENTICAL                                             ~0, 'Linux'
          2      > JMPZ                                                     ~1, ->4
    6     3    >   ECHO                                                     'Linux'
    7     4    > > RETURN                                                   1
Enter fullscreen mode Exit fullscreen mode

FETCH_CONSTANT try to get the value of PHP_OS from the current namespace and it will look into the global namespace as it doesn’t exist here. Then the IS_IDENTICAL instruction executes the IF statement.

Now try adding the backslash to constant:

<?php

namespace App;

if (\PHP_OS === 'Linux') {
    echo "Linux";
}
Enter fullscreen mode Exit fullscreen mode

As you can see in the opcode the engine doesn't need to try to fetch the constant because now it's clear where it is, and since it's a static value it already has it in memory.

Also the IF statement disappeared because the other side of the IS_IDENTITCAL statement is a static string ('Linux') so the IF can be marked as "true" without the overhead of interpreting it on every execution.

This is why you have a lot of power to influence the ultimate performance of your PHP code.

I hope it was an interesting topic, as I mentioned at the beginning of the article I'm getting a lot of benefits from using this tactic and in fact they are also used in our packages.

You can see here an example of how I used this tips in our PHP package to optimize its performance: https://github.com/inspector-apm/inspector-php/blob/master/src/Inspector.php#L302

For more technical articles you can follow me on Linkedin or X.

Monitor your PHP application for free

Inspector is a Code Execution Monitoring tool specifically designed for software developers. You don't need to install anything at the server level, just install the Laravel or Symfony package and you are ready to go.

If you are looking for HTTP monitoring, database query insights, and the ability to forward alerts and notifications into your preferred messaging environment, try Inspector for free. Register your account.

Or learn more on the website: https://inspector.dev

Inspector Laravel Monitoring Dashboard

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .