В этой статье мы рассмотрим результаты нескольких бенчмарков, начиная с 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.
Те же бенчмарки прогонялись и на всех промежуточных релизах, например между 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.
Оптимизированные выражения 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 со следующими параметрами:
Конечно, я компилировал более старые версии, некоторые параметры требовалось отключить или заменить другими. Также не все расширения были доступны или могли быть скомпилированы.
Запуск бенчмарков
Каждый бенчмарк запускался с помощью PHP CLI (Command-Line Interface) через специальный скрипт, который делал следующее:
1) С помощью функции microtime() на лету модифицировал скрипт, чтобы изнутри измерять время его выполнения. После модифицирования скрипт выглядел так:
<?php
$__start__ = microtime( true );
/***
Здесь исходный код бенчмарка
***/
fprintf( STDERR, microtime( true ) - $__start__);
?>
Это делалось для того, чтобы обеспечить стабильность измерений скрипта изнутри, без изменения его поведения.
2) Далее шли два сухих прогона, чтобы исполняемые PHP-файлы и содержимое скрипта бенчмарка оказались в кеше ОС.
3) Скрипт выполнялся пять раз, сохранялись минимальное, максимальное и среднее время выполнения. В этой статье представлены только средние значения — «время выполнения скрипта».
Длительность выполнения измерялась с помощью 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-сообществе. Он основал в FacebookPHP Group, крупнейшую группу, посвящённую отдельному языку программирования. В ней состоят более 140 тыс. участников и 22 модератора. Создатель PHP Расмус Лердорф сказал: «В мире PHP ничего не происходит без движения сообщества». Надеюсь, эти слова будут вдохновлять вас.
Если вы не можете помочь развитию PHP с помощью написания кода на С, то можете выкладывать свои PHP-разработки на GitHub, PHP Classes, Packagist — куда угодно. Чем больше мест, где мы будем делиться друг с другом наработками, тем лучше.
Заключение
Цель статьи — дать представление о производительности разных версий PHP, начиная с 5.0 и заканчивая свежайшей экспериментальной версией. Тестирование выполнялось с помощью известных бенчмарков. Также в статье приведён список улучшений, повысивших производительность различных версий PHP.