Bytecode Optimization
When PHP bytecode is cached and does not have to be generated on each and every request then it makes sense to perform more expensive optimizations that improve the performance even further. This is why OpCache can optionally optimize bytecode.
The bytecode optimizations that OpCache performs are controlled
by its opcache.optimization_level
configuration
directive. Its integer value is a bitmask that controls which
optimization passes are executed.
As is usual for bitmasks, setting
opcache.optimization_level
to -1
will
always enable all available optimization passes while setting it to
0
will disable optimization entirely.
OpCache’s optimizer first transforms the bytecode into Static Single Assignment (SSA) form. This representation “requires that each variable is assigned exactly once, and every variable is defined before it is used”. Once in this form, Type Inference can be used to automatically detect the type of expressions and variables. Now techniques from control-flow and data-flow analysis can be applied to optimize the bytecode.
The concepts mentioned in the previous paragraph are advanced compiler construction topics. Properly explaining and discussing them is outside the scope of this book. You do not need to understand how OpCache works to benefit from its bytecode optimizations. We’re not going to let you off the hook, though, without showing you at least a couple of examples how OpCache optimizes bytecode.
When a variable is written to and read from only once, respectively, then the bytecode operations to write and read that variable can be eliminated. Consider this example:
function f()
{
$result = 'result';
return $result;
}
compiled vars: !0 = $result
line #* E I O op fetch ext return operands
--------------------------------------------------------------------
4 0 E > ASSIGN !0, 'result'
6 1 > RETURN !0
7 2* > RETURN null
The original three instructions (see above) can be reduced to a single instruction (see below).
compiled vars: none
line #* E I O op fetch ext return operands
--------------------------------------------------------------------
6 0 E > > RETURN 'result'
Identifying and eliminating a “dead” variable such as
$result
in the example above is rather simple.
OpCache’s bytecode optimizer is able to eliminate more complicated
code, too, as the following example will show.
function f(int $a, int $b, int $c)
{
$d = $a * $b;
$e = $b * $c;
return $a + $c;
}
In the example shown above, $d = $a * $b
and
$e = $b * $c
are not used and evaluating these
expressions is superfluous. Without so-called escape analysis,
however, PHP does not know this and bytecode is generated that
performs the respective calculations:
compiled vars: !0 = $a, !1 = $b, !2 = $c, !3 = $d, !4 = $e
line #* E I O op fetch ext return operands
--------------------------------------------------------------------
2 0 E > RECV !0
1 RECV !1
2 RECV !2
4 3 MUL ~5 !0, !1
4 ASSIGN !3, ~5
5 5 MUL ~7 !1, !2
6 ASSIGN !4, ~7
7 7 ADD ~9 !0, !2
8 > RETURN ~9
8 9* > RETURN null
When it performs escape analysis to identify operations that have no effect, OpCache’s bytecode optimizer reduces the original ten instructions (see above) to five instructions (see below).
compiled vars: !0 = $a, !1 = $b, !2 = $c
line #* E I O op fetch ext return operands
--------------------------------------------------------------------
2 0 E > RECV !0
1 RECV !1
2 RECV !2
7 3 ADD ~3 !0, !2
4 > RETURN ~3
It goes without saying that you should not have code that can be optimized away like that in the first place. With regard to optimization, the main lesson to be learned is that developers should write readable and maintainable code, and let the compiler take care of optimizations.