Function Inlining in Zend Engine
Modern PHP is fast! It has several performance features such as OPCache, JIT, and other improvements at the compilation step to make smart optimizations for many PHP applications.
Inspecting the OPCodes is an easy way to make sure that PHP can make the best optimizations as possible. With the OPCodes listed, it becomes clearer if a given PHP snippet takes the shortest number of OPCodes necessary to perform the intended task.
PHP has several over 30 such functions at the moment, that make use of special OPCodes, or otherwise inlined to improve performance.
One example to show this effect is the strlen
function. It returns the length of a given string, and PHP tries to optimize preemptively.
if (strlen('Test') < 2) {
echo "Test";
}
In this snippet, the strlen
function is called on a static string literal, and PHP can eliminate this block completely because the length of the Test
string is fixed, and the comparison value is also a static value.
This is better revealed with the OPCode dump.
Prior to optimization
php -d opcache.opt_debug_level=0x10000 test.php
0000 JMPZ bool(false) 0002
0001 ECHO string("Test")
0002 RETURN int(1)
After optimization
php -d opcache.opt_debug_level=0x20000 test.php
0000 RETURN int(1)
This example works the other way too, by getting rid of the unnecessary JMP
/JMPZ
/JMPNZ
OPCodes that would be otherwise used in a PHP if
block.
if (strlen('Test') < strlen('Test Test')) {
echo "Test";
}
0000 ECHO string("Test")
0001 RETURN int(1)
Because strlen('Test') < strlen('Test Test')
always evaluates to true
, the optimized OPCode does not contain any jumps.
Fully-Qualified Function Names
One caveat with the special function handling is that when a special function call is made inside a namespace, the engine cannot use the special handling because it is possible that a function with the same name is declared later in the program.
namespace Foo;
if (strlen('Test') < strlen('Test Test')) {
echo "Test";
}
Notice how the if
block is inside the Foo
namespace. The OPCode dump reveals that PHP did not apply the special handling here:
php -d opcache.opt_debug_level=0x20000 test.php
0000 INIT_NS_FCALL_BY_NAME 1 string("Foo\strlen")
0001 SEND_VAL_EX string("Test") 1
0002 V1 = DO_FCALL_BY_NAME
0003 INIT_NS_FCALL_BY_NAME 1 string("Foo\strlen")
0004 SEND_VAL_EX string("Test Test") 1
0005 V2 = DO_FCALL_BY_NAME
0006 T0 = IS_SMALLER V1 V2
0007 JMPZ T0 0009
0008 ECHO string("Test")
0009 RETURN int(1)
Most of the modern PHP code bases use namespaces, and to bring back the special handling, the function names can be prefixed with a backslash (\
), or aliased with the use function
clause. They are often called as "Fully-Qualified Function Names".
namespace Foo;
if (\strlen('Test') < \strlen('Test Test')) {
echo "Test";
}
or
namespace Foo;
use strlen;
if (strlen('Test') < strlen('Test Test')) {
echo "Test";
}
Both snippets above signal the engine that the strlen
calls are indeed for the internal strlen
function, which allows the engine to go ahead and inline them.
With FQFNs, the engine can again apply its magic:
0000 ECHO string("Test")
0001 RETURN int(1)
The gist of this article is that, when calling PHP internal functions, specially functions with inlining features, always make the fully-qualified function names to enable engine optimizations on them.
List of Functions with Special Handling
All of the following functions have special handling within the Zend Engine, and can benefit from them if called as a Fully-Qualified Function Name.
array_key_exists
array_slice
boolval
call_user_func
call_user_func_array
chr
count
defined
doubleval
floatval
func_get_args
func_num_args
get_called_class
get_class
gettype
in_array
intval
is_array
is_bool
is_double
is_float
is_int
is_integer
is_long
is_null
is_object
is_resource
is_scalar
is_string
ord
sizeof
strlen
strval