В этой статье мы рассмотрим результаты нескольких бенчмарков, начиная с PHP 5 и вплоть до экспериментальной JIT-ветки (сейчас в разработке). На момент написания не было известно, появится ли до PHP 8 ещё какая-то основная версия, например PHP 7.2. Но логично предположить, что возможности экспериментальной ветки как минимум будут включены в PHP 8.
C момента своего появления в 1994-м язык PHP радикально изменился. Первые релизы представляли собой просто внешние CGI-программы, которые создавались во многом как личный проект Расмуса Лердорфа. С третьей версии PHP был серьёзно переработан, возникла группа разработчиков языка.
Благодаря расширяемости PHP 3 функциональность языка стремительно разрасталась. Появлялись базовые и дополнительные расширения, которые привносили новые функции в разные сферы: работу с сетью, парсинг, кеширование и поддержку баз данных.
Развивался и сам язык, в него внесли много улучшений. Появилась поддержка объектно ориентированных конструкций, таких как классы, интерфейсы, трейты, замыкания и т. д.
Но многим разработчикам этого было мало. С ростом популярности языка росли и требования к нему со стороны сообщества, в основном связанные с производительностью, масштабируемостью и более экономным потреблением памяти.
Почти 20 лет создатели языка прилагали огромные усилия, чтобы удовлетворять всевозможные требования. Хотя с появлением PHP 3 производительность существенно возросла, сколько-то серьёзные результаты язык смог продемонстрировать только с PHP 4, когда появился движок Zend.
В 2000-м были внедрены новые in-memory компилятор и модель исполнения (executor model). Это позволило вновь сильно поднять производительность PHP, нередко в 5—10 раз. В результате его начали всерьёз рассматривать как инструмент для создания веб-приложений и сайтов. И сегодня PHP достиг высот, которых никто не ожидал от этого языка, когда он появился.
Но взрывной рост популярности PHP лишь привёл к росту требований о повышении производительности. К счастью, у движка Zend прекрасный потенциал для модернизации.
Хотя PHP 5 не стал заметным шагом вперёд и в некоторых случаях был даже медленнее PHP 4, группа разработчиков Zend постоянно оптимизировала движок от релиза к релизу, в результате PHP 5.6 оказался быстрее в 1,5—3 раза.
Но главный рывок произошёл с выходом PHP 7 в декабре 2015-го. Через год была анонсирована версия 7.1, тоже получившая ряд улучшений.
Компилятор PHP JIT и ожидания по улучшению производительности PHP 8
В настоящее время разрабатывается очень многообещающая версия Zend. Она будет основана на версии из релиза 7.1, а когда именно выйдет, пока не объявлено. Так что сейчас это экспериментальная JIT-ветка.
Одна из главных интриг связана с Just-In-Time (JIT) компиляцией. Это методика преобразования кода в другой формат (нативный машинный код) прямо перед выполнением. Цель JIT — повысить скорость работы программ. Посмотрим, смогут ли разработчики сдержать обещание.
Бенчмарк обработки PHP-скриптов
Для этой статьи использовались бенчмарки, измерявшие производительность обработки скриптов на чисто процессорных задачах, т. е. без операций ввода-вывода: обращений к файлам, подключений к сети или базе данных.
Применялись следующие бенчмарки:
- bench.php — находится в папке php-src/Zend исходного дистрибутива PHP.
- micro_bench.php — там же.
- mandelbrot.php
Бенчмарки прогонялись на последних второстепенных релизах основных версий PHP:
- 5.0.5
- 5.1.6
- 5.2.17
- 5.3.29
- 5.4.45
- 5.5.38
- 5.6.28
- 7.0.13
- 7.1.0
- Экспериментальная JIT-ветка
Те же бенчмарки прогонялись и на всех промежуточных релизах, например между 5.3.0 и 5.3.29. Результаты красноречивы: релизы не демонстрировали заметных улучшений производительности. Улучшения отмечались только при переходах между основными версиями, например с PHP 5.4 на PHP 5.5 или с PHP 5.6 на PHP 7.
Это означает, что те же скрипты будут выполняться примерно с одной скоростью и на PHP 5.4.0, и на PHP 5.4.45.
Подробности о настройке хостовой системы, о выполнении конкретных бенчмарков и об интерпретировании результатов можно почитать тут.
Сравнение результатов процессорных бенчмарков
По каждому бенчмарку приведены три значения:
- Время, с: время выполнения (в секундах).
- Относительное изменение, %: изменение времени выполнения по сравнению с предыдущей версией. Если бенчмарк выполнялся быстрее — значение положительное, если медленнее — отрицательное.
- Абсолютное изменение, крат: насколько быстрее выполнялся скрипт по сравнению с PHP 5.0.
Результаты прогона бенчмарков вы можете увидеть в таблице ниже.
(1) Бенчмарк не может выполняться на версиях до 5.3, потому что он использует свойства, которые ещё не были реализованы.
(2) Результаты в этой колонке немного смещены, потому что бенчмарку для работы нужен как минимум PHP 5.3. Их можно взять просто для справки, раз нельзя сравнить с PHP 5.0.
(3) Это модифицированная версия скрипта mandelbrot.php, который выполнялся слишком быстро в версии 7.1.0 в экспериментальной ветке, так что не получалось точно измерить скорость. Поэтому мы внутри скрипта выполняли сто вычислений, а не одно.
Конечно, чисто процессорные бенчмарки не позволяют оценить все аспекты производительности PHP. Данные могут быть нерепрезентативны. Тем не менее на основе этих данных можно сделать выводы:
- PHP 5.1 более чем вдвое быстрее PHP 5.0.
- Версии 5.2 и 5.3 обладают новым набором улучшений, но не таких впечатляющих, как у 5.1.
- Следующий скачок производительности был у версии 5.4.
- Расширение opcache поставлялось с 5.5 и 5.6. Это позволяло повысить производительность за счёт ускорения загрузки кода, когда один и тот же скрипт выполнялся последовательно. Однако opcache не слишком полезно для скриптов, исполняемых в режиме CLI.
- PHP 7.0 — главный прорыв с точки зрения производительности. Движок Zend был полностью переработан, и мы наблюдаем результат этих масштабных изменений.
- В PHP 7.1 в расширении opcache были оптимизированы опкоды, что объясняет скачок производительности по сравнению с 7.0.
- Экспериментальная JIT-ветка демонстрирует очередной скачок производительности. Но в некоторых случаях никакого улучшения нет. Иногда ветка оказывается даже медленнее, потому что компилирование не ускоряет работу кода. Но не будем забывать, что эта фича пока в разработке.
Сопоставление производительности разных версий PHP
PHP 5 гораздо производительнее, чем PHP 4. Движок Zend, лежащий в основе интерпретатора, был полностью переработан (Zend Engine 2), что открыло дорогу дальнейшим улучшениям. Здесь мы не будем освещать все различия между PHP 4 и PHP 5, вкратце пройдёмся лишь по вещам, внедрённым после PHP 5.0.
Ниже перечислены только те изменения, которые затронули ядро PHP. Более подробный список нововведений и изменений: PHP 5 и PHP 7.
PHP 5.1
- Скомпилированные переменные
- Специализированный исполнитель (Specialized executor)
- Кеш real-path
- Ускоренная обработка выражения
switch()
- Ускоренные функции массивов
- Ускоренное извлечение переменных
- Ускоренный вызов «волшебных» методов
PHP 5.2
- Новый диспетчер памяти
- Оптимизированное копирование массивов/хеш-таблиц
- Оптимизированные выражения
require_once()
иinclude_once()
- Небольшие оптимизации специфических внутренних функций
- Улучшенное компилирование HEREDOC и компилирование интерполированных строк
PHP 5.3
- Сегментированный стек VM
- Бесстековая VM
- Замена констант в ходе компилирования
- Ленивая инициализация таблицы символов
- Улучшение real-path кеша
- Улучшение скорости runtime и потребления памяти
- Ускоренный парсинг языка
- Улучшение размера двоичных PHP-файлов и запуска кода (code startup)
PHP 5.4
- Отложенное размещение хеш-таблицы
- Константные таблицы (Constant tables)
- Рантаймовые кеши привязки (binding caches)
- Интернированные строки (Interned Strings)
- Улучшенный уровень вывода (output layer)
- Улучшена производительность тернарных операторов при использовании массивов
PHP 5.5
- Улучшено соглашение о вызове (calling convention) виртуальной машины
- Интеграция OPcache
- Другие оптимизации движка Zend
PHP 5.6
- Оптимизирована обработка пустых строк, минимизирована необходимость в размещении новых пустых значений
PHP 7 vs. PHP 5.6
Большинство из этих улучшений относятся к движку Zend:
- Рефакторинг основных структур данных
- Улучшена конвенция вызова виртуальной машины
- Новый API парсинга параметров
- Новый диспетчер памяти
- Многочисленные улучшения исполнителя виртуальной машины
- Существенно уменьшено использование памяти
- Улучшены функции
__call()
и__callStatic()
- Улучшена конкатенация строк
- Улучшен поиск символов в строках
PHP 7.1, улучшения производительности
- Новый оптимизационный фреймворк на базе SSA (встроен в opcache)
- Глобальная оптимизация байткода PHP на основе выведения типов (type inference)
- Высокоспециализированные обработчики опкодов виртуальной машины
Свойства PHP 8 или PHP 7.2, экспериментальная JIT-ветка
- Компилирование Just-In-Time
Как измерялась производительность
Прогон бенчмарков был чуть более сложным процессом, чем запуск Unix-команды time
, и проходил в несколько этапов:
Настройка системы
С сделал выделенную систему с такими характеристиками:
- VPS с одним виртуальным ядром, 2,4 ГГц, 2 Гб памяти и два SSD drives — один для ОС, второй для хранения исходных файлов PHP, бинарных файлов и записи отчётов.
- ОС Debian Wheezy 3.2.82-1
- Компилятор Gnu C 4.9.2-10 (дистрибутив Debian Jessie).
Хотя система поставлялась с компилятором Gnu C 4.7.2, пришлось поставить более свежую версию: экспериментальная JIT-ветка должна компилироваться с помощью Gnu C 4.8 и выше.
Компилирование исходного кода
Перед сборкой полных дистрибутивов был запущен скрипт configure со следующими параметрами:
--prefix=/usr/local/php
--disable-debug
--disable-phpdbg
--enable-mysqlnd
--enable-bcmath
--with-bz2=/usr
--enable-calendar
--with-curl
--enable-exif
--enable-fpm
--with-freetype-dir
--enable-ftp
--with-gd
--enable-gd-jis-conv
--enable-gd-native-ttf
--with-gettext=/usr
--with-gmp
--with-iconv
--enable-intl
--with-jpeg-dir
--enable-mbstring
--with-mcrypt
--with-openssl
--enable-pcntl
--with-pdo-mysql=mysqlnd
--with-png-dir
--with-recode=/usr
--enable-shmop
--enable-soap
--enable-sockets
--enable-sysvmsg
--enable-sysvsem
--enable-sysvshm
--enable-wddx
--with-xmlrpc
--with-xsl
--with-zlib=/usr
--enable-zip
--with-mysqli=mysqlnd
Конечно, я компилировал более старые версии, некоторые параметры требовалось отключить или заменить другими. Также не все расширения были доступны или могли быть скомпилированы.
Запуск бенчмарков
Каждый бенчмарк запускался с помощью PHP CLI (Command-Line Interface) через специальный скрипт, который делал следующее:
1) С помощью функции microtime()
на лету модифицировал скрипт, чтобы изнутри измерять время его выполнения. После модифицирования скрипт выглядел так:
<?php
$__start__ = microtime( true );
/***
Здесь исходный код бенчмарка
***/
fprintf( STDERR, microtime( true ) - $__start__);
?>
Это делалось для того, чтобы обеспечить стабильность измерений скрипта изнутри, без изменения его поведения.
2) Далее шли два сухих прогона, чтобы исполняемые PHP-файлы и содержимое скрипта бенчмарка оказались в кеше ОС.
3) Скрипт выполнялся пять раз, сохранялись минимальное, максимальное и среднее время выполнения. В этой статье представлены только средние значения — «время выполнения скрипта».
Использовались такие настройки в php.ini:
engine = On
short_open_tag = Off
realpath_cache_size = 2M
max_execution_time = 86400
memory_limit = 1024M
error_reporting = 0
display_errors = 0
display_startup_errors = 0
log_errors = 0
default_charset = "UTF-8"
[opcache]
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.fast_shutdown=1
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.use_cwd=1
opcache.max_accelerated_files=100000
opcache.max_wasted_percentage=5
opcache.memory_consumption=128
opcache.consistency_checks=0
opcache.huge_code_pages=1
// PHP 8/Next only
opcache.jit=35
opcache.jit_buffer_size=32M
Интерпретирование результатов
Длительность выполнения измерялась с помощью Unix-команды time
. Пример выходных данных:
$ time php bench.php
real: 0m1.96s
user: 0m1.912s
sys: 0m0.044s
Значение real
— это время от вызова команды до её прерывания (пока не происходит возврата к командной строке).
Значение user
— время, потраченное на выполнение пользовательского кода (в данном случае — исполняемого PHP-файла).
Значение sys
— время, потраченное на выполнение кода ОС (kernel). Это значение должно быть минимальным, но может оказаться сильно больше представленного, если ваш код обращается, например, к медленным устройствам. Также на величину значения способна повлиять высокая загруженность ОС.
На системах в состоянии ожидания суммарное значение user + sys
должно быть очень близко к real
. В приведённом выше примере: user + sys = 1,956 с, real = 1,960 с
. Разница в 0,004 с связана не с нашим процессом, а с разными задачами ОС, например с диспетчеризацией.
Тот же скрипт был выполнен на высоконагруженной ОС при параллельном компилировании тремя разными PHP-версиями:
$ time php bench.php
real: 0m7.812s
user: 0m2.02s
sys: 0m0.101s
Как видите, уровень нагрузки сильно влияет на время выполнения (возможно, и на системное время). Поэтому я добавил в бенчмарк ещё одно значение — overhead
операционной системы. Это разница между полным временем выполнения (elapsed time
) и суммой пользовательского и системного времени.
Я удостоверился, чтобы во время прогона бенчмарков это значение в течение 99 % времени было меньше 100 миллисекунд, даже когда выполнение скриптов занимало десятки секунд.
Спасибо Дмитрию Стогову и всей команде разработки PHP
Эта статья писалась при активной помощи Дмитрия Стогова. Он прояснил ряд моментов и рецензировал представленную здесь информацию.
Дмитрий был разработчиком расширения Turck MMCache, которое со времён PHP 4 может использоваться для кеширования PHP-опкодов в совместно используемой памяти. После этого Дмитрий начал работать над Zend, чем и занимается по сей день.
Также он когда-то инициировал создание PHPNG — того, что позднее превратилось в PHP 7. В работе над этой и последующими версиями с Дмитрием сотрудничали Никита Попов и Синьчэнь Хуэй (Xinchen Hui).
В создание PHP 5 внесли большой вклад Энди Гутманс, Зеев Сураски и Стас Малышев. Многих других разработчиков я не стану здесь перечислять, чтобы не загромождать статью.
Специальное благодарственное видео для всех, кто помогал развивать PHP
В 2016-м исполнился 21 год со дня появления PHP — 8 июня 1995 г.
Чтобы отдать должное всем, кто так или иначе внёс свой вклад в развитие PHP, Питер Кокот с помощью Gource создал анимационное видео. В нём рассказывается о развитии ключевых модулей PHP в течение всей жизни языка.
Питер Кокот хорошо известен в PHP-сообществе. Он основал в Facebook PHP Group, крупнейшую группу, посвящённую отдельному языку программирования. В ней состоят более 140 тыс. участников и 22 модератора. Создатель PHP Расмус Лердорф сказал: «В мире PHP ничего не происходит без движения сообщества». Надеюсь, эти слова будут вдохновлять вас.
Если вы не можете помочь развитию PHP с помощью написания кода на С, то можете выкладывать свои PHP-разработки на GitHub, PHP Classes, Packagist — куда угодно. Чем больше мест, где мы будем делиться друг с другом наработками, тем лучше.
Заключение
Цель статьи — дать представление о производительности разных версий PHP, начиная с 5.0 и заканчивая свежайшей экспериментальной версией. Тестирование выполнялось с помощью известных бенчмарков. Также в статье приведён список улучшений, повысивших производительность различных версий PHP.