Harness PHP 7’s Performance Boosts: Part 1

This entry is part 1 of 2 in the series PHP 7's Performance Boosts

Learn how to harness PHP 7’s performance boosts using some of its key optimizations in this two-part series by Andrew Caya, the creator of Linux for PHP, the lead developer of a popular Joomla extension, and a contributor to many open source projects.

PHP 7 optimizations

PHP 7 is a major optimization in itself. A good part of PHP’s code base was rewritten for this release, and most official benchmarks show that generally speaking almost any PHP code will run about two times faster or more with PHP 7 than with previous versions.

PHP is programmed in C, and optimizing the performance of Zend’s Ahead-Of-Time (AOT) compiler depends ultimately on using the C compiler’s internal logic in an optimized way. This latest version of PHP is the result of many years of research and experiments by Zend. The greater part of these optimizations was implemented by eliminating the performance overhead generated by certain PHP internal structural constructs and data structures.

According to Dmitry Stogov [1]a typical real-life PHP application spends about 20% of the CPU time in the memory manager, 10% doing hash table operations, 30% in internal functions, and only 30% in the VM. In order to optimize the execution of the PHP code, PHP 7’s new version of the Zend Engine had to start by representing source code in an Abstract Syntax Tree (AST).

This allowed the engine to generate better quality Intermediate Representations (IR) of the source code. Since PHP 7.1, you can remove dead code and reduce as many expressions as possible to their static representation through Static Single Assignment (SSA) form and type inference. In turn, this allows the engine to only allocate necessary data structures to the stack instead of the heap in memory at runtime.

This is very important as it allows you to see why datatype juggling and dynamic structures in general will create most of the overhead by bloating memory allocation at runtime, why certain data structures had to be re-implemented to allow for C-level performance, and why immutability is a developer’s ally when trying to achieve better code performance.

Now, have a look at these elements more closely.

Strict typing

When a language is dynamically typed that is, it has loosely typed variables, it provides a higher level of abstraction that boosts the developer’s productivity, but doesn’t offer the best performance since its compiler has more work to do when trying to determine the datatypes of its variables. It comes as no surprise that strongly typed languages have always had better performance at runtime than loosely typed ones.

This conclusion was confirmed by Facebook’s HipHop project, which conducted benchmark tests with different languages and came to the conclusion that statically compiled languages always execute more quickly and consume less memory than dynamic ones.

Although PHP 7 is still a loosely typed language, it now offers the possibility to strict-type variables and function signatures. This can be easily tested by executing the following code to see its current performance:

<br />
// chap3_strict_typing.php </p>
<p>declare(strict_types = 0); </p>
<p>$start = microtime(true); </p>
<p>function test ($variable)<br />
{<br />
    $variable++; </p>
<p>    return &quot;$variable is a test.&quot;;<br />
} </p>
<p>ob_start(); </p>
<p>for ($x = 0; $x &lt; 1000000; $x++) { </p>
<p>    $array[$x] = (string) $x; </p>
<p>    echo test($array[$x]) . PHP_EOL; </p>
<p>} </p>
<p>$time = microtime(true) - $start; </p>
<p>ob_clean(); </p>
<p>ob_end_flush(); </p>
<p>echo 'Time elapsed: ' . $time . PHP_EOL; </p>
<p>

 

Here are the results of running this script using Blackfire.io:

The profiling report when omitting to do strict typing of variables and function signatures

Now, replace the code with the following:

<br />
// chap3_strict_typing_modified.php</p>
<p>declare(strict_types = 1);</p>
<p>$start = microtime(true);</p>
<p>function test (int $variable) : string<br />
{<br />
    $variable++;</p>
<p>    return $variable . ' is a test.'; </p>
<p>} </p>
<p>ob_start(); </p>
<p>for ($x = 0; $x &lt; 1000000; $x++) { </p>
<p>   $array[$x] = (int) $x; </p>
<p>   echo test($array[$x]) . PHP_EOL; </p>
<p>} </p>
<p>$time = microtime(true) - $start; </p>
<p>ob_clean(); </p>
<p>ob_end_flush(); </p>
<p>echo 'Time elapsed: ' . $time . PHP_EOL;<br />

 

If you run the previous code with PHP 5.6, you consume almost 7.4 MB of memory and the elapsed time is 0.005 seconds:

The results when running the script against PHP 5.6

If you run the same code with PHP 7, you’ll get the following result:

The results when running the same script against PHP 7.1

The results are impressive. The same script is 40 times faster and consumes almost 10 times less memory. Immutable arrays therefore provide more speed and developers should avoid modifying large arrays and encourage the use of packed arrays as much as possible when dealing with large arrays in order to optimize memory allocation and maximize speed at runtime.

Memory allocation of integers and floats

Another optimization introduced by PHP 7 is the reuse of previously allocated variable containers. If you need to create a large number of variables, you should try to reuse them, as PHP 7’s compiler will avoid reallocating memory and reuse the memory slots that are already allocated. Now have a look at the following example:

</p>
<p>// chap3_variables.php</p>
<p>$start = microtime(true);</p>
<p>for ($x = 0; $x &lt; 10000; $x++) {<br />
    $$x = 'test';<br />
}</p>
<p>for ($x = 0; $x &lt; 10000; $x++) {<br />
    $$x = $x;<br />
}</p>
<p>$time = microtime(true) - $start;</p>
<p>echo 'Time elapsed: ' . $time . PHP_EOL;</p>
<p>echo memory_get_usage() . ' bytes' . PHP_EOL;</p>
<p>

Run this code against PHP 5.6 and PHP 7 in order to see the difference in memory consumption. You can start with PHP 5.6:

The results when running the script against PHP 5.6

Now, run the same script with PHP 7:

The results when running the same script against PHP 7.1

As you can see, the results show that memory consumption was reduced by almost a third. Although this goes against the very principle of the immutability of variables, it is still a very important optimization when you must allocate a large number of variables in memory.

String interpolation and concatenation

In PHP 7, string interpolation has been optimized with a new string analysis algorithm. This means that string interpolation is now much faster than concatenation and what used to be true about concatenation and performance is no longer the case. Now, look at the following code example in order to measure the new algorithm’s performance:

</p>
<p>// chap3_string_interpolation.php</p>
<p>$a = str_repeat(chr(rand(48, 122)), rand(1024, 3000));</p>
<p>$b = str_repeat(chr(rand(48, 122)), rand(1024, 3000));</p>
<p>$start = microtime(true);</p>
<p>for ($x = 0; $x &lt; 10000; $x++) {</p>
<p>    $$x = &quot;$a is not $b&quot;;</p>
<p>}</p>
<p>$time = microtime(true) - $start;</p>
<p>echo 'Time elapsed: ' . $time . PHP_EOL;</p>
<p>echo memory_get_usage() . ' bytes' . PHP_EOL; </p>
<p>

Here are the performance measurements when running this code against PHP 5.6:

The results when running the script against PHP 5.6

And here is the same script with PHP 7:

The results when running the same script against PHP 7.1

PHP 7 is about three to four times faster and consumes more than a third less memory allocation. The lesson to be learned here is to try using PHP 7’s string interpolation algorithm as much as possible when dealing with strings.

Parameter references

Even though it is best to avoid passing a variable by reference to a function in order to avoid altering your application’s state outside of the function, PHP 7 makes it possible to pass variables by reference to functions in a highly optimized way even if the reference is a mismatch. Now, look at the following code example in order to better understand how PHP 7 is much more efficient in doing so than PHP 5:

<br />
// chap3_references.php</p>
<p>$start = microtime(true);</p>
<p>function test(&amp;$byRefVar)<br />
{</p>
<p>    $test = $byRefVar;</p>
<p>}</p>
<p>$variable = array_fill(0, 10000, 'banana');</p>
<p>for ($x = 0; $x &lt; 10000; $x++) {</p>
<p>    test($variable);</p>
<p>}</p>
<p>$time = microtime(true) - $start;</p>
<p>echo 'Time elapsed: ' . $time . PHP_EOL;</p>
<p>echo memory_get_usage() . ' bytes' . PHP_EOL;<br />

Run this code with the PHP 5 binary:

The results when running the script against PHP 5.6

Here is the result when executing the same code with PHP 7:

The results when running the same script against PHP 7.1

The results are once more very impressive as PHP 7 does the same work with almost a third less memory allocation and 1,000 times faster! What’s happening under the hood is that PHP 7 no longer makes copies in memory of variables when a reference mismatch occurs. Thus, the new compiler avoids bloating memory allocation for nothing and speeds up the execution of any PHP script where reference mismatches are an issue.

This concludes part 1 of the series. In part 2, you’ll identify more possible optimizations. Stay tuned! If you liked this article, you can refer to Andrew Caya’s Mastering The Faster Web with PHP, MySQL, and JavaScript. This book will help you boost the performance of any Web application using advanced PHP, SQL and JavaScript techniques and make it part of what has come to be known as the Faster Web.

 

Series NavigationPHP 7’s Performance Boosts :More Possible Optimizations >>

Share your thoughts