The PHP JIT RFC is a hot topic on the internals list right now and the voting has started for it to be included in PHP 8.0 and as experimental feature in 7.4.
I wanted to test it out myself, here are the steps necessary to get started on a Linux (Ubuntu) server (or desktop):
git clone https://github.com/php/php-src.git
cd php-src
git remote add zendtech https://github.com/zendtech/php-src.git
git checkout zendtech/jit-dynasm-7.4
./buildconf
./configure --prefix=/opt/php/php-7.4 --enable-opcache --enable-opcache-jit --with-zlib --enable-zip --enable-json --enable-sockets --without-pear
make -j4
sudo make install
For testing I needed a more realistic problem that was bit more complex than PHPs internal benchmark (which the JIT doubles in speed).
Luckily I came across a good one at Symfony User Group in Cologne this week: The react-php-redis server by @another_clue re-implements redis server in PHP with almost zero PHP extension dependencies. That means the vanilla build from above with no dependencies is enough to get it running.
In addition it fully works with the redis-benchmark
command that the
original redis-server package includes, so it takes no effort to make some
tests. The benchmark pegs the PHP redis server to 100% making it a good
candidate for testing JIT.
The code is doing async I/O so the Redis protocol parsing and internal handling should play a significant role in this code that might be optimizable by the JIT.
git clone https://github.com/clue/php-redis-server.git
cd php-redis-server/
composer install
I ran it without the JIT:
/opt/php/php-7.4/bin/php bin/redis-server.php --port 6380
And with the JIT use these flags:
/opt/php/php-7.4/bin/php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=50000000 -dopcache.jit=1235 bin/redis-server.php --port 6380
The -dopcache.jit=1235
only jits the HOT functions that are called often.
Then to check their performance in relation to each other, I ran redis-benchmark
-p 6380 -q
against the servers (from the redis-tools
package).
Don't take my numbers for gold (I ran them on my busy desktop machine), but you can see a 4-23% improvement depending on the benchmarked command.
Benchmark | Nojit | Jit | % change |
---|---|---|---|
PING_INLINE | 30674.85 | 31877.59 | 3.92% |
PING_BULK | 87873.46 | 95969.28 | 9.21% |
SET | 81766.15 | 87336.24 | 6.81% |
GET | 81433.22 | 91575.09 | 12.45% |
INCR | 77881.62 | 83682.01 | 7.45% |
LPUSH | 71275.84 | 79617.83 | 11.70% |
RPUSH | 67294.75 | 79239.3 | 17.75% |
LPOP | 73529.41 | 84530.86 | 14.96% |
RPOP | 76103.5 | 80450.52 | 5.71% |
SADD | 84745.77 | 89686.1 | 5.83% |
HSET | 82712.98 | 91074.68 | 10.11% |
SPOP | 87260.03 | 99700.9 | 14.26% |
LPUSH (needed to benchmark LRANGE) | 68493.15 | 83822.3 | 22.38% |
LRANGE_100 (first 100 elements) | 21743.86 | 26759.43 | 23.07% |
LRANGE_300 (first 300 elements) | 9825.11 | 11923.21 | 21.35% |
LRANGE_500 (first 450 elements) | 6819.42 | 8272.67 | 21.31% |
LRANGE_600 (first 600 elements) | 5120.33 | 5707.11 | 11.46% |
MSET (10 keys) | 45998.16 | 52631.58 | 14.42% |
Looking at the results in a system profiler (`perf`) I can see that the PHP process is spending a lot of time in I/O functions, so these numbers are not showing the full potential of the JIT with CPU bound code.