PHP 7 vs. PHP 5: A Performance Comparison

About the Aerospike PHP Client, Zend Engine, and PHPNG

The Aerospike API Client for PHP is built on top of the Aerospike C Client as an extension for PHP. If you are not familiar with the PHP Client, please refer to our PHP repository on Github. Zend Engine (ZE) is the open source scripting engine that interprets the PHP programming language. PHP Next Generation (PHPNG) is the refactored Zend Engine, which aims to offer better performance and was used as the basis for PHP 7.

What’s New in PHP 7?

The main focus of PHP 7 is to optimize the structure that represents a variable in PHP—Zend values (zvals). In PHP 7, zvals are not heap allocated and no longer store a refcount. Instead, the refcount is stored by any complex values the zval points to (such as strings, arrays, or objects).

The fact that simple values are not heap allocated in PHP 7 eliminates the need for refcounting, which in and of itself results in a significant performance boost. Storing refcount in the values themselves also allows them to be shared independently of the zval structure, which is another advantage over PHP 5.

Benchmarking Performance Improvements

We used the performance suite built into the Aerospike PHP Client to measure the performance improvements realized by upgrading the client from PHP 5 to PHP 7. Specifically, we used the multi-process script (rw-worker.php), which is suitable for what we aim to achieve—measuring how both PHP clients behave under high load (multi-process). We ran this script with different read/write (GET/PUT) ratios, four workers (processes), and 100,000 iterations.

For example, the following script was used to start four concurrent workers processing 50,000 transactions each, with a 9:1 read/write ratio:

./rw-concurrent.sh -h 192.168.119.3 -c 4 -n 50000 -w 10 run.log

The script launches four concurrent rw-worker.php scripts, waits on them to finish, and calls the rw-summary.php script to combine and display the results to a given log file.

In this benchmark, we are particularly interested in measuring total execution time (sec), combined throughput (TPS) on the client side, time spent inside each operation (μs), and memory consumption (MB).

To measure the time spent inside PUT and GET operations within Aerospike, we used Xdebug’s profiler. Xdebug’s built-in profiler is a powerful tool; it helps find bottlenecks in PHP scripts and can visualize those issues using an external tool such as KCacheGrind (used here). Profiling is enabled by setting the xdebug.profiler_enable to 1 in php.ini.

Our test launched four processes, each running 100,000 transactions. All runs were performed against a one-node cluster running Aerospike Server Community Edition version 3.9.1 on CentOS 7 with 32 Intel(R) Xeon(R) CPU E5-2660 @ 2.20GHz processors (with hyperthreading turned on) and 32GB of memory. The test namespace was set up as in-memory storage with a replication factor of 1. We set transaction-queues to 32, transaction-threads-per-queue to 4, and service-threads to 32.

We ran the client on the same machine as the server and used PHPBrew to build and install multiple PHP versions on the same machine to make sure the same environment was used for each client. The two PHP versions we used were php-7.0.10 and php-5.5.38 with Aerospike PHP Client v3.4.10.

Let’s see how both clients behave with workloads of 50/50 reads/writes, 100% reads, and 100% writes:

php1

Table 1. Summary of Results for PHP 5

php2

Table 2. Summary of Results for PHP 7

php3

Figure 1. Throughput Comparison (Higher is Better)

php4

Figure 2. Comparison of Total Execution Time (Lower is Better)

Screenshots taken from KCacheGrind (Figures 3 and 4) show time per call data for 50/50 Reads/Writes. The Self column shows (if absolute cost is selected) the total time spent in the function. The time is defined in units, where 1 unit is 1/1,000,000th of a second. The Callers column on the Time per Call tab displays the time spent per call.is displayed on the Time per Call tab under Callers on the right hand side of Figure 3. Count represents the number of iterations set in the rw-worker.php. A relative cost (%) is also available.

php5

Figure 3. PHP 5 – Time per Call (50/50 Reads/Writes)

php6

Figure 4. PHP 7 – Time per Call (50/50 Reads/Writes)

Memory Consumption

PHP 7’s performance improvements are the result of significant memory savings.

In order to measure memory consumption and quantify memory usage improvements, we used two PHP functions: memory_get_usage, which returns the amount of memory (in bytes) that’s currently being allocated to the PHP script, and memory_get_peak_usage, which returns the peak usage of memory (in bytes) allocated to the PHP script.

In order to showcase the effect of memory allocations, we used a different script in which we created a new variable key for each iteration and used that key to PUT and GET records. This results in each process inserting a new record instead of writing to the same key, as was previously the case.

Note: This will increase the memory usage per iteration, which is what we are looking for—how do both clients perform when memory allocation is frequent?

Here are the results:

php7

Table 3. Summary of Memory Usage Comparison

php8

Figure 5. Memory Usage Comparison (Lower is Better)

As expected, PHP 7 uses less memory due to the new zval representation and the new hash table implementation.

Resources

The PHP scripts used for the performance measurements are based on scripts that are part of the Aerospike PHP Client package and can be found in the performance folder. However, to showcase the PHP 7 new features (mainly hash tables), we increased the number of bins so we can store multiple heterogeneous complex data types within the same record, thereby increasing the object size. We also used another script for measuring memory usage.

Both the scripts and the Aerospike config file used for this test can be found on GitHub.

Conclusion

One of the reasons we found PHP 7 to be faster for the Aerospike PHP Client is because we use a lot of hash tables in the C level. The new hash table implementation of the Zend Engine is more efficient than the previous implementation. This results in a clear performance gain. Also, using complex Aerospike objects means that more efficient PHP 7 code can be executed at the C level, thus manipulating more hash tables through the new hash table iteration function.

Although we were expecting the new Aerospike Client based on PHP 7 to be more efficient, this performance comparison underscores and quantifies the performance improvements that PHP 7 brought to our client.

Even this basic test using simple data types demonstrates the improved performance of PHP 7 over PHP 5. More complex transactions will see even bigger gains in performance from PHP 7 as they typically require more memory allocations; these are where the biggest gains in performance in PHP 7 will be seen.

As always, we need your help and input to continue to improve and enhance the Aerospike experience. Feel free to contribute your feedback, ideas, and questions to our user forum, file GitHub issues, or create a pull request for the next great feature you’d like to contribute to the Aerospike user community!