18 April 2017

Сравнение производительности версий PHP

Mail.ru Group corporate blogHigh performancePHPSystem Analysis and DesignDesigning and refactoring
Original author: Christian Vigh

В этой статье мы рассмотрим результаты нескольких бенчмарков, начиная с 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-скриптов


Для этой статьи использовались бенчмарки, измерявшие производительность обработки скриптов на чисто процессорных задачах, т. е. без операций ввода-вывода: обращений к файлам, подключений к сети или базе данных.


Применялись следующие бенчмарки:



Бенчмарки прогонялись на последних второстепенных релизах основных версий 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.

Результаты прогона бенчмарков вы можете увидеть в таблице ниже.


image


(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


Эта статья писалась при активной помощи Дмитрия Стогова. Он прояснил ряд моментов и рецензировал представленную здесь информацию.


image


Дмитрий был разработчиком расширения Turck MMCache, которое со времён PHP 4 может использоваться для кеширования PHP-опкодов в совместно используемой памяти. После этого Дмитрий начал работать над Zend, чем и занимается по сей день.


Также он когда-то инициировал создание PHPNG — того, что позднее превратилось в PHP 7. В работе над этой и последующими версиями с Дмитрием сотрудничали Никита Попов и Синьчэнь Хуэй (Xinchen Hui).


В создание PHP 5 внесли большой вклад Энди Гутманс, Зеев Сураски и Стас Малышев. Многих других разработчиков я не стану здесь перечислять, чтобы не загромождать статью.


Специальное благодарственное видео для всех, кто помогал развивать PHP


В 2016-м исполнился 21 год со дня появления PHP — 8 июня 1995 г.


image


Чтобы отдать должное всем, кто так или иначе внёс свой вклад в развитие PHP, Питер Кокот с помощью Gource создал анимационное видео. В нём рассказывается о развитии ключевых модулей PHP в течение всей жизни языка.



Питер Кокот хорошо известен в PHP-сообществе. Он основал в Facebook PHP Group, крупнейшую группу, посвящённую отдельному языку программирования. В ней состоят более 140 тыс. участников и 22 модератора. Создатель PHP Расмус Лердорф сказал: «В мире PHP ничего не происходит без движения сообщества». Надеюсь, эти слова будут вдохновлять вас.


Если вы не можете помочь развитию PHP с помощью написания кода на С, то можете выкладывать свои PHP-разработки на GitHub, PHP Classes, Packagist — куда угодно. Чем больше мест, где мы будем делиться друг с другом наработками, тем лучше.


Заключение


Цель статьи — дать представление о производительности разных версий PHP, начиная с 5.0 и заканчивая свежайшей экспериментальной версией. Тестирование выполнялось с помощью известных бенчмарков. Также в статье приведён список улучшений, повысивших производительность различных версий PHP.

Tags:phpпроизводительностьникто не читает теги
Hubs: Mail.ru Group corporate blog High performance PHP System Analysis and Design Designing and refactoring
+74
34.4k 103
Comments 103
Top of the last 24 hours