Меню

Почему не будет php 6

Введение в PHP 7: Что добавлено, что убрано

Постойте, а где же PHP 6?

Если вы какое-то время не работали с PHP, вы можете удивиться, куда же пропал PHP 6, почему мы перепрыгнули с PHP 5 сразу на PHP 7? Что ж, я буду краток, выпуск PHP шестой версии не состоялся. Главной особенностью версии 6 была поддержка символов Unicode, поскольку в основном PHP используется в веб-разработке, вебу же нужна поддержка Unicode, поэтому её реализация имела смысл.

Идея была в том, чтобы реализовать поддержку Unicode непосредственно в ядре. Такой подход должен был расширить возможности языка — от использования глупых эмоджи в качестве имен переменных и функций, до мощной функциональности интернациональных строк. Например, в случае, когда другой язык использует заглавные и прописные буквы отлично от английского языка или же при конвертации имени на китайском в имя на английском языке.

PHP6 был амбициозным, но отстойным. Посему мы занялись PHP7, в процессе пропустив шестую версию.

К сожалению, этот амбициозный план имел куда больше проблем, чем ожидалось. Большая часть кодовой базы должна была быть портирована для поддержки Unicode, как в случае ядра, так и в случае важных расширений, что оказалось утомительным и непростым делом. Это замедлило процесс разработки других возможностей языка, что расстроило многих PHP-разработчиков. Появились и другие барьеры, что привело к падению интереса к разработке встроенной поддержки Unicode, а со временем проект и вовсе оказался заброшенным.

А поскольку масса ресурсов, таких как книги и статьи, были написаны с использованием названия PHP 6 и поддержки Unicode, новой версии было решено присвоить имя PHP 7 — просто для того, чтобы избежать недопонимания.

Как бы там ни было, хватит копаться в мрачном прошлом. Давайте взглянем на то, что нового у нас появилось в PHP 7.

Войны за производительность, PHP 7 vs. PHP 5

Практически все обновления привносили небольшие улучшения производительности. Однако на этот раз производительность PHP, по сравнению с более ранними версиями, выросла куда более существенно, став одной из наиболее привлекательных особенностей PHP 7. Это было частью проекта “PHPNG” (“php new generation” или “php нового покления”, — прим. переводчика), затронувшего собственно сам Zend Engine.

Рефакторинг внутренних структур данных и добавление дополнительного этапа перед компиляцией кода в виде абстрактного синтаксического дерева — Abstract Syntax Tree (AST), привели к превосходной производительности и более эффективному распределению памяти. Цифры сами по себе выглядят многообещающе — тесты, выполненные на реальных приложениях показывают, что PHP 7 в среднем вдвое быстрее PHP 5.6, а также использует на 50% меньше памяти вовремя обработки запросов, что делает PHP 7 сильным соперником для компилятора HHVM JIT от Facebook. Взгляните на эту инфографику от Zend, которая отображает производительность для некоторых общеизвестных CMS и фреймворков.

PHP 7 выглядит знакомо, но он “заточен” под производительность. Усовершенствованный Zend Engine и итоговый прирост производительности привели к огромной разнице между ним и предыдущей версией.

Уменьшение потребления памяти позволяет более слабым компьютерам лучше обрабатывать запросы, предоставляя возможность выстраивания микросервисов вокруг PHP. Внутренние изменения, в частности реализация AST, также открывает возможности для будущих оптимизаций, которые могут ещё больше увеличить производительность. Новая собственная реализация JIT-компилятора разработана с намерением развития в будущих версиях.

Синтаксический сахар в PHP 7

В PHP 7 появилось несколько новых синтаксических штучек. Почему бы и не расширить некоторые возможности самого языка, если они предлагают лучший или более легкий способ написания приятного глазу кода?

Группировка объявлений импорта

Теперь мы можем группировать объявления импорта классов, находящихся в одном пространстве имён, в одной строке. Это поможет нам выровнять декларации неким, наделенным смыслом, образом или же просто сэкономит пару байт вашего кода.

Или же, если вы предпочитаете многострочный стиль:

Null-коалесцентный оператор

Решает распространенную проблему в PHP. Она возникает в случае, если мы хотим присвоить значение переменной, которое присвоено другой переменной, но если последней переменной значение не было присвоено, то присвоить некое явное значение. Часто проявляется при работе с пользовательским вводом.

Можно использовать с цепочкой переменных:

Оператор “космический корабль”

Оператор “космический корабль” позволяет проводить трехуровневое сравнение двух значений, позволяя понимать не только их равенство или неравенство, но и то, которое из них больше при неравенстве, возвращая 1,0 или -1.

В этом случае мы можем предпринимать различные действия в зависимости от того, как различаются значения:

Сравниваемые значения могут иметь тип integer, float, string и даже быть массивами. Как разные значения сравниваются друг с другом? Смотрите в документации.

Новое в PHP 7

Конечно, в PHP 7 появилась новая, впечатляющая функциональность.

Типы скалярных параметров и подсказки (hints) по возвращаемым типам

В PHP 7 расширили ранее существовавшее объявление параметров в методах (классах, интерфейсах и массивах) путем добавления четырех скалярных типов — целого (int), с плавающей запятой (float), логического (bool) и строкового (string) в качестве возможного типа параметра.

Кроме того, опционально мы можем указать тип результата, возвращаемого функцией или методом. Поддерживаются типы bool, int, float, string, array, callable, имя класса или интерфейса и parent (для методов класса).

Объявление типов позволит строить прозрачные приложения, избегая передачи и возврата неверных значений при работе с функциями. Другие плюсы — появление анализаторов статического кода и IDE, предлагающих более ясное отображение кода при отсутствии документирующих примечаний DocBlocks.

Поскольку PHP слабо типизированный язык, некоторые значения параметров и возвращаемых типов будут приводиться исходя из контекста. Если мы передаем значение “3” в функцию, имеющую объявленный параметр типа int, интерпретатор будет рассматривать его как целое и не сгенерирует ошибку. Если вас не устраивает такое поведение, вы можете работать в строгом режиме — strict mode — путем добавки соответствующей директивы.

Она устанавливается отдельно для каждого файла, поскольку глобальная опция разделила бы хранилища кода на такие, в которых файлы разрабатывались с включенной опцией и те, где она была отключена, что привело бы к непредсказуемому поведению при работе с кодом из двух хранилищ с разными настройками.

Исключения движка

С появлением исключений уровня ядра, фатальные ошибки, которые ранее могли привести к остановке выполнения скрипта, теперь могут быть легко перехвачены и обработаны.

Ошибки, такие как вызов несуществующего метода, теперь не остановят скрипт, вместо этого будет сгенерировано исключение, которое можно обработать в блоке try catch, что явно улучшает обработку ошибок в вашем приложении. Это важно для некоторых типов приложений, серверов и демонов, поскольку фатальные ошибки, в противном случае, вполне могли привести к необходимости их рестарта. Тесты в PHPUnit также должны стать более удобными в использовании, поскольку фатальные ошибки могли уронить весь тестировочный проект. Исключения, в отличие от ошибок, могут быть обработаны для каждого теста отдельно.

PHP 7 выглядит и ощущается знакомым инструментом, но он нацелен на высокую производительность. Переработанный Zend Engine и прирост скорости работы приводят к большим отличиям от предыдущей версии.

В PHP 7 появилось достаточно много новых классов исключений, призванных обрабатывать типы ошибок, с которыми вы можете столкнуться. Для обеспечения совместимости между версиями, добавлен новый интерфейс Throwable, он может быть реализован как исключениями уровня ядра, так и пользовательскими исключениями. Такой подход реализован с целью недопущения наследования базового класса исключений исключениями ядра, что привело бы к появлению исключений в прежде написанном коде, которых ранее не было.

До PHP 7 такой код привел бы к фатальной ошибке исполнения скрипта:

Анонимные классы

Анонимные классы — двоюродные братья анонимных функций, которые вы могли использовать при создании функциональности коротких и ясных объектов. Анонимные классы легко создаются и используются так же, как и обычные объекты. Вот пример из документации.

Использование анонимного класса:

Анонимные классы полезны при тестировании юнитами, в частности при мокинге (имитации поведения реального объекта — прим. переводчика) при тестировании объектов и сервисов. Их наличие позволит нам избежать использования больших мокинг-библиотек и фреймворков путем создания простого объекта, поддерживающего интерфейс, который мы можем использовать для мокинга.

Функции CSPRNG

Две новых функции для генерации крипографически безопасной строки и целых. Первая возвращает случайную строку длиной $len:

Вторая возвращает число в диапазоне $min… $max.

Синтаксис Escape-кода для Unicode

В отличие от многих других языков, до PHP версии 7, в PHP не было способа указать в строке escape-последовательность для Unicode символа. Теперь с помощью escape-последовательности \u можно генерировать такие символы с помощью их кода из набора UTF-8. Это лучше, чем непосредственная вставка символов, лучше контролируются невидимые символы и символы, имеющие графическое отображение отличное от значения:

Помните, что код, ранее работавший и использовавший пару символов \u, не будет корректно работать в версии 7 из-за изменившегося поведения.

Обновленные генераторы

Генераторы в PHP тоже получили несколько приятных новых возможностей. Теперь у них появился оператор return, который может быть использован для выдачи некоторого финального значения, актуального на момент завершения итерации. Его можно использовать для проверки корректности выполнения генератора. Например, узнать, выполнился ли он без ошибок, что позволит коду, вызвавшему генератор, корректно обработать любую возникшую ситуацию.

Больше того, генераторы могут возвращать и выдавать выражения из других генераторов. Таким образом, можно разбивать сложные операции на более простые.

Ожидания (expectations)

Ожидания (expectations) — улучшение функции assert() с сохранением обратной совместимости. Они позволяют использовать утверждения с нулевой стоимостью (zero-cost assertions) в рабочем коде и поддерживают возможность генерации пользовательского исключения при возникновении ошибки при отработке утверждения, что может быть полезно при разработке.

Функция assert() стала языковой конструкцией в PHP 7. Утверждения должны быть использованы только во время разработки и тестирования с целью отладки. Для настройки её поведения мы должны использовать две директивы.

  • zend.assertions
    1: генерируем и выполняем код (режим разработки) (значение по умолчанию)
    0: генерирует код, но обходит его во время выполнения
    -1: не генерирует код, делая его кодом с нулевой стоимостью (режим рабочего кода)
  • assert.exception
    1: генерируется при ошибке утверждения путем создания соответствующего объекта исключения или же путем генерации объекта AssertionError, если такой объект не был создан
    0: использует или генерирует Throwable так, как было описано выше, но генерируется только предупреждение (warning) на базе того объекта, а не генерация исключения с его помощью (поведение, совместимое с PHP 5)

Готовимся к переходу от PHP 5 к PHP 7

Появление версии PHP с номером 7 предоставило возможность изменения/обновления и даже удаления функциональности, которая считалась устаревшей или, некоторое время, уже ненужной. Такие изменения могут привести к проблемам с обратной совместимостью старых приложений.

Другая проблема, возникающая при появлении подобных версий, заключается в том, что библиотеки и фреймворки, которые для вас важны, могут не иметь обновлений, совместимых с новым релизом. Команда разработчиков PHP пыталась вносить изменения как можно в более высокой степени сохранив обратную совместимость для того, чтобы миграция на новую версию была как можно менее трудоёмкой. Относительно новые и вовремя обновляемые приложения, скорее всего, будет проще перевести на седьмую версию, тогда как для старых приложений необходимо взвешенное решение — стоят ли потраченные усилия полученных возможностей или же лучше и вовсе не обновляться.

Большая часть проблем невелики и могут быть легко устранены, в то время как другие могут потребовать куда больше времени и усилий. В целом, если вы видели предупреждения об использовании устаревших функций до установки PHP 7, то вы, скорее всего, увидите сообщения об ошибках, которые прекратят выполнение вашего приложения до их устранения.

Вас предупредили, так ведь?

Старые SAPI и расширения

Куда важнее тот факт, что были убраны старые и ненужные SAPI, такие как расширение mysql (но вы же его уже не используете, так?). Полный список расширений и возможностей, которые были удалены, вы можете посмотреть здесь и здесь.
Другие расширения и SAPI были портированы на PHP 7.

Очень много старых SAPI и расширений было убрано из PHP 7. Мы считаем, что за ними скучать не станут.

Однообразный синтаксис описания переменных

Это обновление привнесло некоторые изменения в части согласованности для конструкций переменная-переменная. Оно позволит использовать более прогрессивные выражения с переменными, что, в отдельных случаях, приведет к изменению поведения кода, как показано ниже:

Читайте также:  Почему на цепочке нет пробы

Это изменит поведение приложений, получающих доступ к переменным указанным способом. С другой стороны, вы сможете выделывать вот такие фокусы:

Убраны тэги в старом стиле

Убраны или более некорректны открывающие/закрывающие тэги

Заменить их на корректные легко, но их использование в наше время выглядит несколько странно, не так ли?

Некорректные имена для классов, интерфейсов, трейтов

В результате добавлений, таких как типы возвращаемых значений и параметров, классы, интерфейсы и трейты больше не могут именоваться такими именами:

Подобное именование может привести к некорректной работе существующих приложений и библиотек, но легко поддаётся исправлению. Следующие имена тоже нежелательно использовать в выше названном качестве, поскольку они зарезервированы на будущее:

Воздержитесь от использования этих слов в качестве имен, и вы сэкономите себе время впоследствии.

Полный список изменений, приводящий к несовместимости, можно посмотреть здесь.
Также вы можете использовать php7cc, этот инструмент проверит ваш код и отобразит потенциальные проблемы, которые могут возникнуть при переходе на PHP 7.

Но нет пути лучше, чем установить PHP 7 и увидеть всё воочию.

Потенциальные проблемы совместимости PHP 7

Совместимость инфраструктуры PHP 7

Многие сервисы хостинга добавили поддержку PHP 7. Это хорошая весть для провайдеров shared-хостинга, поскольку возросшая производительность позволит им увеличить число клиентских веб-сайтов без обновления железа, уменьшив текущие расходы и увеличив доходы. Что до клиентов, то они не должны ожидать резкого увеличения производительности в этих условиях, да и, говоря откровенно, shared-хостинг в любом случае не опция в случае приложения, ориентированного на производительность.

С другой стороны, сервисы, предлагающие частные виртуальные сервера или выделенные сервера, получат все прелести роста производительности. Некоторые PaaS-сервисы, такие как Heroku, давно поддерживают PHP 7, другие же, типа AWS Beanstalk и Oracle OpenShift, плетутся позади. Убедитесь на веб-сайте вашего PaaS-провайдера, есть ли у него поддержка PHP 7 или же она планируется в ближайшем будущем.

IaaS-провайдеры позволяют вам контролировать “железо” и устанавливать PHP 7 (или скомпилировать, если вам это больше нравится). Пакеты PHP 7 уже доступны для большинства IaaS-сред.

Совместимость программного обеспечения с PHP 7

В довесок к совместимости с инфраструктурой, вам также необходимо помнить о потенциальных проблемах совместимости с программным обеспечением. Известные CMS типа WordPress, Joomla и Drupal уже добавили поддержку PHP 7. Основные фреймворки, такие как Symfony и Laravel тоже сделали это.

Однако пришло время предостережений. Эта поддержка не распространяется на код третьих лиц в виде дополнений, плагинов, пакетов и т.п., к чему обращается ваш CMS или фреймворк. Проблемы могут быть, и ваша задача заключается в том, чтобы убедиться — всё готово для работы под PHP 7.

Для активных, поддерживающихся репозиториев, это не должно быть проблемой. Но заброшенные репозитории без поддержки PHP 7 могут сделать всё ваше приложение непригодным к использованию.

Будущее PHP

В PHP 7 был убран устаревший код, а также был дан зеленый свет новым возможностям и будущим улучшениям в области эффективности. Плюс, PHP вскоре должен получить оптимизацию производительности. Несмотря на частичную потерю обратной совместимости с предыдущими версиями, большинство возникающих проблем легко решаемы.

Библиотеки и фреймворки мигрируют на PHP 7, что приводит к появлению их новых версий. Я призываю вас попробовать семёрку и оценить полученные результаты. Может быть, ваше приложение уже совместимо с новой версией и готово работать на PHP 7, получая выгоды от его использования.

источник

PHP / PHP 6 не будет, не осилили


Забавно, но не нашёл на хабре упоминания об этом даже в комментариях. Пора устранить этот недостаток, ведь многие используют только хабр, как источник информации.
Так вот PHP 6 не будет, вообще. 11 марта 2010 команда разработчиков приняла решение об отмене выпуска PHP 6 в текущем его виде. В результате транк с PHP 6 был перенесён в бранч, а в транке образовалась новая версия — 5.4, в которую разработчики перенесли все наработки из PHP 6, кроме юникода.
Ниже приведен краткий пересказ презентации (pdf), сделанной Andrei Zmievski на PHP Community Conference в 2011 году.
Но для начала рассмотрим как юникод поддерживается сейчас
В исходном коде:
Можно использовать в названии функций и переменных нелатинские символы, попадающие под регулярку [a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*. Следующий код абсолютно валиден: function привет()<>

Нормально обрабатываются документы с UTF-8 BOM, но только если нет пустых строк, иначе произойдёт отправка заголовков. Чтобы избежать этого рекомендуется держать HTML-код в шаблонах, а в самих PHP-файлах не использовать закрытие PHP-тега ?>

При обработке строк:
Стандартные строковые функции, нормально переваривают как минимум UTF-8, но с символами работать не умеют, поэтому такие вещи как определение позиции символа или размера строки будут работать не правильно.
Стандартное расширение xml умеет перегонять текст из UTF-8 в ISO-8859-1 и обратно, но по сути это бесполезный функционал.
Расширение mbstring (Multibyte String), которое поддерживает большинство кодировок, включая юникодовые. Правда обычно оно не включено по умолчанию.

Так а в чём проблема спросите вы? Есть mbstring, используй и радуйся.
А дело в том, что разработчики решили поддержать юникод не на уровне библиотеки, а на уровне ядра. Это означает, что любая php-функция, а также строковые операторы, в потенциале, могут принимать в себя юникод и более менее гарантировать, что юникод символы не будут исковерканы, удалены и не произойдёт ошибки. Кроме того, сам mbstring реализует далеко не все стандартные строковые функции.
Взгляните на следующий примеры:
и
это максимально наглядные примеры того, что подразумевается под поддержкой на уровне ядра.
Выбор кодировки
UTF-8 — Достоинства: обратная совместимость в ASCII, преобладающая кодировка в вебе, есть много библиотек, поддерживающих её.

UTF-16 — Достоинства: внутренняя кодировка библиотеки ICU, которая была выбрана для юникода и используется крупными игроками в вебе.

UTF-32 — Достоинства: прямая индексация.

В результате выбор пал, на UTF-16 из-за библиотеки ICU. Надо отметить, что оглядываясь назад, разработчики заявили, что если бы им дали сделать выбор ещё раз, то они выбрали бы UTF-8, так как режим обратной совместимости снизил бы объём работы.
Что пошло не так

Во-первых: достаточно мало специалистов, которые понимают тонкости юникода и использования ICU.
Во-вторых: это технически сложная задача встроить юникод везде.
В-третьих: люди просто устали вставлять поддержку юникода и в без того прекрасно работающий код.
Результат

Поскольку в культуре PHP-разработчика принято фокусироваться на интересной работе, а поддержка юникода не является интересной работой, то проект выдохся.
Будущее

Расширение mbstring неполноценно. Можно написать отдельный класс для работы с юникодом, но это также будет не полноценно. Поэтому как именно нужно поддерживать юникод в PHP сейчас всё ещё открытый вопрос.
Выученные уроки

Переписывать очень много работающего кода — трудно.

Заставлять людей делать утомительные вещи — трудно.

Ожидать результаты от вышеописанной деятельности, длящейся долгое время — трудно.

источник

Как мы сделали PHP 7 в два раза быстрее PHP 5. Часть 1: оптимизация структур данных

В декабре 2015 вышел PHP 7.0. Компании, которые перешли на «семерку» отметили, что увеличилась производительность, а нагрузка на сервера — уменьшилась. Первыми перешли на семерку Vebia и Etsy, а у нас Badoo, Авито и OLX. Для Badoo переход на семёрку обошелся в 1 млн. долларов экономии на серверах. Благодаря PHP 7 в OLX средняя нагрузка на сервер снизилась в 3 раза, повысилась эффективность и экономия ресурсов.

Дмитрий Стогов из Zend Technologies на HighLoad++ рассказал, благодаря чему повысилась производительность. В расшифровке: о внутреннем устройстве PHP, об идеях в основе версии 7.0, об изменениях в базовых структурах данных и алгоритмах, которые и определили успех.

Disclaimer: На март 2019 года 80% сайтов работают на PHP, и 70% из них — на PHP 5, хотя с 1 января 2019 эта версия не поддерживается. Доклад Дмитрия от 2016 года про принципы, благодаря которым произошел двукратный скачок производительности между PHP 5 и 7, — актуален и в марте 2019. Для половины сайтов — точно.

О спикере: Дмитрий Стогов начал программировать еще в 80-х: «Электроника Б3-34», Basic, ассемблер. В 2002 Дмитрий познакомился с PHP и вскоре, начал работать над его усовершенствованием: разработал Turck MMCache для PHP, руководил проектом PHPNG и играл важную роль в работе над JIT для PHP. Последние 14 лет Principal Engineer в Zend Technologies.

Zend Technologies занимается разработкой PHP и коммерческих решений на его основе. В 1999 её основали израильские программисты Энди Гутманс и Зеев Сураски, которые за два года до этого создали PHP 3. Эти люди стояли у истоков разработки PHP и во многом определили текущий вид языка и успех технологии.

Zend Technologies разрабатывает ядро PHP и приложения для него, и за время работы мне приходилось писать расширения, влезать во все подсистемы и даже заниматься коммерческими проектами, иногда с PHP совсем не связанными. Но самой интересной темой для меня всегда была производительность.

Искать пути ускорения PHP я начал еще до прихода в Zend, работая над своим собственным проектом, который конкурировал с компанией. За время работы над проектом я досконально разобрался в языке и понял, что работая не с мейнстримным проектом, можно влиять только на отдельные аспекты исполнения скрипта, а все самое интересное и эффективное можно создать только в ядре. Это понимание и стечение обстоятельств привели меня в Zend.

Небольшой экскурс в историю PHP

PHP – это не совсем и не только язык программирования. PHP расшифровывается как Personal Home Page — инструмент создания персональных веб-страниц и динамических веб-сайтов. Язык – только одна из его основных частей. PHP — это огромная библиотека функций, множество расширений для работы с другими сторонними библиотеками, например, для доступа к БД или к парсерам XML, а также набор модулей для связи с различными веб-серверами.

Датский программист Расмус Лердорф представил PHP в июне 1995. На тот момент это был просто набор CGI-скриптов, написанных на Perl. В апреле 96 Расмус представил PHP/FI, а уже в июне вышла версия PHP/FI 2.0. Впоследствии эту версию существенно переработали Энди Гутманс и Зеев Сураски, и в 98-м выпустили PHP 3.0. К 2000 году язык пришел к тому виду, который мы привыкли видеть сегодня как с точки зрения языка, так и внутренней архитектуры — PHP 4, основанный на Zend Engine.

С 4-й версии PHP развивается эволюционно. Переломным моментом был выход PHP 5 в 2004, когда полностью обновилась объектная модель. Именно она открыла эру PHP фреймворков и поставила вопрос о производительности на новый уровень. Предвидя это, сразу после выхода 5.0 мы в Zend задумались об ускорении PHP и принялись работать над повышением производительности.

Версия 7.1, которая вышла в ноябре 2016 на синтетических тестах в 25 раз быстрее версии 2002 года. По графику изменения производительности в разных ветках, основные прорывы видны в 5.1 и 7.0.

В версии 5.1 мы только запустили работу над производительностью, и все за что брались — получалось, но после 5.3 — уперлись в стену, все попытки усовершенствовать интерпретатор ни к чему не приводили.

Тем не менее мы нашли, куда копать, и получили даже больше, чем ожидали, — 2,5-кратное ускорение по сравнению с предыдущей версией 5.6 на тестах. Но самое интересное, что то же 2,5-кратное ускорение мы получили и на неизменных реальных приложениях. Это феномен, потому что предыдущий фактор 2 мы нарабатывали в течении всей жизни пятерки за 10 лет.

Огромный скачок в 5.1 на синтетических тестах, на реальных приложениях не заметен. Причина в том, что при разных использованиях производительность PHP упирается в тормоза, связанные с разными подсистемами.

История PHP 7 начинается с трехлетнего застоя, который начался в 2012, а закончился в 2015 с релизом седьмой версии. Тогда мы поняли, что не можем больше увеличивать производительность мелкими усовершенствованиями нашего интерпретатора и обратились в сторону JIT.

Блуждание около JIT

Почти два года мы потратили на прототип JIT для PHP-5.5. Сначала мы генерировали очень простой код – последовательность вызовов для стандартных обработчиков, что-то наподобие сшитого кода Форта. Затем написали собственный Runtime Assembler, инлайнили отдельный код для обходов, но поняли, что такие низкоуровневые оптимизации не дают практического эффекта даже на тестах.

Читайте также:  Почему вылетает autumn aurora 2

Тогда мы задумались о выводе типов переменных, используя методы статического анализа. Реализовав вывод, сразу же получили 2-кратное ускорение на тестах. Воодушевленные, попытались написать глобальные register allocator, но потерпели неудачу. Мы использовали достаточно высокоуровневое представление, а для распределения регистров применять его было практически невозможно.

Чтобы избежать проблем с низким уровнем, решили попробовать LLVM, и через год у нас получилось 10-кратное ускорение для bench.php, а на реальных приложениях — ничего. Кроме того, компиляция реальных приложений теперь занимала минуты, например, первый реквест к WordPress занимал 2 минуты и не давал ускорения. Конечно, это совершенно не подходило для реальной практики.

Хороший код возможен при правильном предсказании типов, которое в реальных приложениях работает плохо, а использование структур данных РНР делает генерируемый код неэффективным.

Что же тормозит?

Мы переосмыслили причины неудач и решили еще раз посмотреть, почему тормозит PHP. На картинке результат профилирования нескольких запросов к домашней странице WordPress.

На интерпретацию байт-кода тратится меньше 30%, 20% — это накладные расходы memory-менеджера, 13% — это работа с хэш-таблицами, и 5% — работа с регулярными выражениями.

Работая на JIT, мы избавлялись только от первых 30%, а все остальное лежало мертвым грузом. Практически везде мы были вынуждены использовать стандартные структуры данных PHP, которые влекли за собой накладные расходы: распределение памяти, подсчет ссылок, и т.п. Это понимание и привело к выводу о необходимости замены ключевых структур данных в PHP. С этой подмены фундамента и начался проект PHPNG.

PHPNG. New Generation

Проект получил развитие после безрезультатных попыток создать JIT для PHP. Основная цель — достичь нового уровня производительности и заложить базу для будущих улучшений.

Мы обещали себе какое-то время больше не использовать для измерения производительности синтетические тесты — это как правило небольшие вычислительные программы, которые используют ограниченный объем данных, полностью помещающийся в кэш процессора. Реальные приложения, наоборот, подвержены тормозам, связанным с подсистемной памяти, и одно чтение из памяти может стоить 100 вычислительных инструкций. Проект PHPNG — это рефакторинг ключевых структур данных PHP для оптимизации обращения к памяти. Никаких нововведений, 100% совместимость с PHP 5.

Как менять эти структуры было понятно. Но объем зависимых изменений был огромен, потому что само ядро PHP – это 150 000 строк, и почти каждая третья нуждалась в изменении. Прибавьте ещё сотню расширений, которые входят в base distribution, десяток модулей для разных веб-серверов, и вы поймете грандиозность проекта.

Мы даже не были уверены, что доведем проект до конца. Поэтому запустили проект в тайне и открыли его, только когда появились первые оптимистичные результаты. Две недели ушло на то, чтобы просто скомпилировать ядро. Еще через две недели заработал bench.php. Полтора месяца потратили для обеспечения работы WordPress. Еще через месяц мы открыли проект — это был май 2014 года. На тот момент у нас было ускорение на 30% на WordPress. Это уже казалось грандиозным событием.

PHPNG сразу вызвал волну интереса, и в августе 2014 принят, как основа для будущего PHP 7. Это был уже другой проект, с другим набором целей, где производительность была только одной из них.

PHP 7.0

Сам номер версии 7 был под вопросом. Предыдущая версия была пятая. А шестая разрабатывалась несколько лет назад и была полностью посвящена нативной поддержке Unicode, но неудачные решения, принятые на ранних этапах разработки, привели к чрезмерному усложнению кода ядра и каждого расширения. В конце концов было принято решение о заморозке проекта.

К этому времени уже было накоплено много материала, посвящённого PHP 6: выступления на конференциях, опубликованные книги. Чтобы никого не путать, мы назвали проект PHP 7, пропустив PHP 6. Этой версии повезло куда больше — PHP 7 вышел в декабре 2015, почти по плану.

Кроме производительности, в PHP 7 появились некоторые давно востребованные нововведения:

  • Возможность определять скалярные типы параметров и возвращаемых значений.
  • Исключения вместо ошибок — теперь мы можем их ловить и обрабатывать.
  • Появились Zero-cost assert() , анонимные классы, чистка неконсистентностей, новые операторы и функции ( , ??).

Нововведения это хорошо, но вернемся ко внутренним изменениям. Поговорим о пути, который прошел PHP 7, и о том, куда этот путь нас может завести.

Это основная структура данных PHP. Она используется для представления любого значения в PHP. Так как язык у нас динамически типизированный и тип переменных может меняться во время выполнения программы, нам необходимо хранить поле типа (zend_uchar type), которое может принимать значения IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_ARRAY, IS_OBJECT и т.д., и собственно значение, представленное union-ом (value), где может храниться целое, вещественное число, строка, массив или объект.

zval в PHP 5

Память под каждую такую структуру выделялась отдельно в Heap. Помимо типа и значения в ней же хранился счетчик ссылок на структуру. Так структура занимала 24 байта, не считая накладные расходы memory-менеджера и указателя на нее.

На картинке справа сверху показаны структуры данных, которые создавались в памяти PHP 5 для простого скрипта.

На стеке выделилась память под 4 переменные, представленные указателями. Сами же значения (zval) лежат в куче. В нашем случае это всего два zval, на каждый из которых ссылаются две переменные, и соответственно их счетчики ссылок установлены равными 2.

Для доступа к типу или к скалярному значению нужно как минимум два чтения: сначала прочитать значение указателя, а потом значение структуры. Если же надо прочитать не скалярное значение, а например, часть строки или массива, то потребуется как минимум еще на одно чтение больше.

zval в PHP 7

Там, где раньше мы использовали указатели, в семерке мы стали встраивать zval. Мы ушли от подсчета ссылок для скалярных типов. Поля тип и значение остались без существенных изменений, но добавились еще некоторые флаги и зарезервированное место, про которые расскажу чуть позже.

Слева — как это выглядело в PHP 5, а справа — в PHP 7.

Теперь на стеке лежат сами zval. Для чтения типов и скалярных значений достаточно всего одной машинной инструкции. Все значения сгруппированы в одной области памяти, а это значит, что при работе с локальными переменными у нас практически не будет потерь из-за промахов кэша процессора. Но настоящая мощь нового представления включается при необходимости копирования.

Копирование записи

В верхней строчке скрипта добавилось еще одно присваивание.

В PHP5 мы выделяли из кучи память под новый zval, инициализировали его int(2), изменяли значение указателя переменной b и уменьшали reference counter того значения, на которое b ссылалось раньше.

В PHP 7 мы просто инициализировали переменную b прямо по месту с помощью нескольких инструкций, в то время как в PHP 5 это требовало сотен инструкций. Так zval выглядит сейчас в памяти.

Это два 64-битных слова. Первое слово — значение: целое число, вещественное или указатель. Во втором слове тип (он говорит, как интерпретировать значение), флаги, и зарезервированное место, которое все равно добавилось бы при выравнивании. Но оно не пропадает, а используется разными подсистемами для хранения косвенно связанных значений.

Флаги — это набор битов, где каждый бит говорит о том, поддерживает ли zval какой-то протокол. Например, если стоит IS_TYPE_REFCOUNTED , то при работе с данным zval, engine должен заботиться о значении счетчика ссылок. При присваивании — увеличивать, при выходе из области видимости — уменьшать, если reference counter достиг нуля — уничтожать зависимую структуру.

Из типов, по сравнению с PHP 5, появилось несколько новых.

  • IS_UNDEF — маркер неинициализированной переменной.
  • На смену единому IS_BOOL пришли раздельные IS_FALSE и IS_TRUE .
  • Добавился отдельный тип для ссылок и еще несколько магических типов.

Типы от IS_UNDEF до IS_DOUBLE — скалярные, и не требуют дополнительной памяти. Для их копирования достаточно скопировать первое машинное 64-битное слово со значением и половину второго с типом и флагами.

Refcounted

С другими типами сложнее. Все они представлены подчиненной структурой, и в zval хранится просто ссылка на эту структуру. Для каждого из типов эта структура своя, но в терминах ООП все они имеют общего абстрактного предка или структуру zend_refcounted. Она определяет формат первого 64-битного слова, где хранится счетчик ссылок и другая информация для сборщика мусора.

Это слово можно рассматривать просто как информацию для сборщика мусора, а структуры для конкретных типов добавляют свои поля вслед за этим первым словом.

Строки

В семёрке для строки мы храним вычисленное значение хэш-функции, её длину и сами символы. Размер такой структуры переменный и зависит от длины строки. Хэш-функция вычисляется для строки один раз, при первой необходимости. В PHP 5 она заново вычислялась при каждой потребности.

Теперь строки стали reference countable, и если в PHP 5 мы копировали сами символы, то теперь достаточно увеличить счетчик ссылок на данную структуру.

Так же как и в PHP 5 у нас осталось понятие неизменяемых или interned-строк. Они обычно существуют в одном экземпляре, живут до конца запроса и могут вести себя как скалярные значения. Нам незачем заботиться о счетчике ссылок на них, и для копирования достаточно скопировать только сам zval с помощью четырех машинных инструкций.

Массивы

Массивы представлены встроенной хэш-таблицей и мало чем отличаются от PHP 5. Сама хэш-таблица изменилась, но об этом отдельно.

Массивы теперь — это адаптивная структура, которая немного меняет свою внутреннюю структуру и поведение в зависимости от хранимых данных. Если мы храним только элементы с близкими числовыми ключами, то получаем доступ к элементам непосредственно по индексу со скоростью, сравнимой со скоростью массивов в С. Но стоит в этот же самый массив добавить элемент со строковым ключом — он превращается в настоящий хэш с разрешением коллизий.

Так хэш-таблица выглядит в PHP 5.

Это классическая реализация хэш-таблицы с разрешением коллизий с помощью линейных списков (показана в правом верхнем углу). Каждый элемент представлен Bucket. Все Buckets связаны двусвязными списками для разрешения коллизий, и связаны еще другим двусвязным списком для итерации по порядку. Значения под каждый zval выделяются отдельно — в Bucket мы храним только ссылку на него. Также строковые ключи могут выделяться отдельно.

Таким образом, под каждую хэш-таблицу нужно выделять очень много мелких блоков памяти, а чтобы потом что-то найти, приходится бегать по указателям. Каждый такой переход может вызвать cahce miss и задержку на

Вот что получилось в PHP 7.

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

На картинке, внизу от базового указателя лежат элементы, а вверху — хэш-массив, который адресуется по хэш-функции. Для плоских или упакованных массивов, когда мы храним только элементы с числовыми индексами, верхняя часть вообще не выделяется, и мы адресуемся к Bucket-ам непосредственно по номеру.

Для обхода элементов мы последовательно перебираем их сверху вниз или снизу вверх, что современные процессоры делают безупречно. Значения встроены в Buckets, а вот зарезервированное место в них как раз используется для разрешения коллизий. Там хранится индекс другого Bucket с тем же значением хэш-функции либо маркер конца списка.

Память под строковые значения ключей выделяется отдельно, но это все те же zend_string. При вставке в массив достаточно увеличить reference counter строки, хотя раньше нам приходилось копировать непосредственно символы, а при поиске мы теперь можем сравнивать не символы, а сами указатели на строки.

Неизменяемые массивы

Раньше у нас были неизменяемые строки, а теперь появились еще и неизменяемые массивы. Как и строки они не используют счетчик ссылок и не уничтожаются до конца запроса. Это простой скрипт, который создает массив из миллиона элементов, а каждый элемент — это один и тот же массив с единственным элементом «hello».

В PHP 5 на каждой итерации цикла создавался новый пустой массив, в него записывалось «hello», и все это добавлялось в результирующий массив. В PHP 7 на этапе компиляции мы создаем всего один неизменяемый массив, который ведет себя как скаляр, и добавляем его в результирующий. На представленном примере это позволяет добиться более чем 10-кратного уменьшения потребления памяти и почти 10-кратного ускорения.

Читайте также:  Почему наш организм работает слажено

Константные массивы из миллионов элементов в реальных приложениях, конечно, встречаются не часто, но небольшие — довольно часто. На каждом из них вы получите небольшой, но выигрыш.

Объекты

Ссылки на все объекты в PHP 5 лежали в отдельном хранилище, а в zval был только handle — уникальному ID объекта.

Чтобы добраться до объекта, мы производили как минимум 3 чтения. Кроме того, память под значение каждого свойства объекта распределялась отдельно, и нам требовалось еще как минимум 2 чтения, чтобы прочитать его.

В PHP 7 мы смогли перейти к прямой адресации.

Теперь адрес zend_object доступен с помощью одной машинной инструкции. А Property встроены и для их чтения нужно всего одно дополнительное чтение. Также они сгруппированы вместе, что улучшает data locality и помогает современным процессорам не спотыкаться.

Кроме предопределенных property здесь же хранится ссылка на класс данного объекта, некие handlers — аналог таблиц виртуальных методов, и хэш-таблица для property, которые не были определены. В PHP к любому объекту можно добавить property, которые изначально не были определены, и если для доступа к предопределенным property достаточно нескольких машинных инструкций, то для не предопределенных придется обращаться к хэш-таблице, что потребует десятков машинных инструкций. Конечно, это намного дороже.

Reference

Наконец, нам пришлось ввести отдельный тип для представления PHP ссылок.

Это абсолютно прозрачный тип. Он не виден PHP скриптам. Скрипты видят другой zval, который встроен в структуру zend_reference. Подразумевается, что на одну такую структуру у нас ссылаются как минимум из двух мест, и reference counter этой структуры всегда больше 1. Как только счетчик падает до 1, ссылка превращается в обычное скалярное значение. Встроенный в ссылку zval копируется в последний ссылающийся на него zval, а сама структура удаляется.

Кажется, что теперь работа с reference намного сложнее, чем с другими типами (и это действительно так), но на самом деле в PHP 5 нам приходилось выполнять сопоставимую по сложности работу при обращении к любому значению (даже простому целому числу). Теперь же мы применяем более сложные протоколы только к одному типу и тем самым ускорили работу со всеми другими, особенно со скалярными значениями.

IS_FALSE и IS_TRUE

Я уже говорил, что единый тип IS_BOOL был разбит на отдельные IS_FALSE и IS_TRUE. Эта идея была подсмотрена в реализации LuaJIT, а сделана для ускорения одной из наиболее частых операций — условного перехода.

Если в PHP 5 требовалось прочитать тип, проверить на boolean, прочитать значение, узнать true оно или false и сделать переход на основании этого, то теперь достаточно просто проверить тип и сравнить его с true:

  • если он равен true, то идем по одной ветке;
  • если он меньше true, идем по другой ветке;
  • если он больше true, идем на так называемый slow path (медленный путь) и там проверяем, что это за тип пришел и что с ним делать: если это integer, то мы должны сравнить его значение с 0, если float — опять с 0 (но вещественным), и т.д.

Calling Convention

Изменение в Calling Convention или соглашении о вызовах функций — важная оптимизация, которая затрагивает не только структуры данных, но и базовые алгоритмы. На картинке слева небольшой скрипт, состоящий из функции foo() и ее вызова. Ниже — байт-код, в который этот скрипт был скомпилирован PHP 5.

Сначала расскажу, как это работало в PHP 5.

Calling Convention в PHP 5

Первая инструкция SEND_VAL должна была отправить значение «3» в функцию foo. Для этого она была вынуждена аллоцировать новый zval на куче, копировать туда значение (3) и записать на стек значение указателя на эту структуру.

Аналогично со второй инструкцией. Дальше DO_FCALL инициализировал CALL FRAME , резервировал место под локальные и временные переменные, и передавал управление на вызываемую функцию.

Первый оператор RECV проверял первый аргумент и инициализировал на стеке слот соответствующей локальной переменной ($a). Тут мы обошлись без копирования и просто увеличили счетчик ссылок соответствующего параметра (zval со значением 3). Аналогично второй оператор RECV устанавливал связь между переменной $b и параметром 5.

Дальше тело функции. Произошло сложение 3 + 5 — получилось 8. Это временная переменная и ее значение хранилось непосредственно на стеке.

RETURN и мы возвращаемся из функции.

При возврате освобождаем все переменные и аргументы, которые вышли из области видимости. Для этого проходим по всем zval на которые ссылаются слоты из освобождаемого фрейма, и для каждого уменьшаем счетчик ссылок. Если он достиг 0, то уничтожаем соответствующую структуру.

Как видно, даже такая простая операция, как посылка константы в функцию требует распределения новой памяти, копирования и увеличения счетчика ссылок, а потом еще и двойного уменьшения и удаления.

Calling Convention в PHP 7

В PHP 7 эти проблемы исправили — теперь на стеке храним не указатели на zval-ы, а сами zval-ы.

Также мы ввели новую инструкцию INIT_FCALL , которая теперь отвечает за инициализацию и выделение памяти под CALL FRAME , и резервацию места под аргументы и временные переменные.

SEND_VAL 3 теперь просто копирует аргумент в первый слот за CALL FRAME . Следующий SEND_VAL 5 во второй слот.

Дальше самое интересное. Казалось бы, DO_FCALL должна передать управление на первую инструкцию вызываемой функции. Но аргументы уже попали в слоты, которые зарезервированы для переменных параметров $a и $b, и инструкциям RECV просто ничего делать. Поэтому можно их просто пропустить. Мы посылали два параметра, поэтому пропускаем две инструкции. Если бы посылали три — пропустили бы три.

Так что мы переходим непосредственно на тело функции, производим сложение и возвращаемся.

При возврате чистим все локальные переменные, но теперь только для двух слотов, а поскольку там у нас скаляры, нам опять ничего не требуется делать.

Мой рассказ чуть-чуть упрощен, он не учитывает функции с переменным числом аргументов и необходимость проверки типов и некоторые другие моменты.

Новый Calling Convention немного сломал совместимость. В PHP есть такие функции, как func_get_arg и func_get_args . Если раньше они возвращали оригинальное значение посланного параметра, то теперь возвращают текущее значение соответствующей локальной переменной, потому что мы просто не храним оригинальные значения. Так же как делают отладчики C.

Кроме того, функция теперь не может иметь несколько параметров с одинаковым именем. Смысла в этом не было и раньше, но такой PHP код foo($_, $_) я встречал. На что это похоже? (Я узнал Prolog)

Новый менеджер памяти

Закончив с оптимизацией структур данных и базовых алгоритмов, мы еще раз обратили внимание на все тормозящие подсистемы. Менеджер памяти в PHP 5 занимал почти 20% процессорного времени на WordPress.

После того, как мы избавились от множества аллокаций, его накладные расходы стали меньше, но все равно существенны — и не потому что он делал какую-то существенную работу, а потому, что спотыкался на кэше. Происходило это из-за того, что мы использовали классический алгоритм Doug Lea’s malloc, который подразумевал поиск подходящих свободных участков памяти с помощью путешествия по ссылкам и деревьям, а все эти путешествия неминуемо вызывали промахи кэша.

Сегодня существуют новые алгоритмы управления памятью, которые учитывают особенности современных процессоров. Например: jemalloc и ptmalloc от Google. Сначала, мы попытались использовать их в неизменном виде, но не получили выигрыша, поскольку отсутствие специфичного для PHP функционала удорожало полное освобождение памяти в конце реквеста. В итоге мы отказались от dlmalloc и написали что-то свое, скомбинировав идеи из старого memory manager и jemalloc.

Мы сократили накладные расходы Memory Manager до 5%, уменьшили издержки памяти на служебную информацию и улучшили использование кэша CPU. Подходящие блоки памяти теперь ищутся по битовым картам, память под блоки небольшого размера выделяется из отдельных страниц и кэшируется при освобождении, добавлены специализированные функции для часто используемых размеров блоков.

Множество мелких усовершенствований

Я рассказал только о самых главных усовершенствованиях, но мелких было куда больше. Могу отметить некоторые из них.

  • Быстрый API для разбора параметров внутренних функций и новый API для итерации по HashTable.
  • Новые инструкции VM: конкатенация строк, специализация, супер-инструкции.
  • Некоторые внутренние функции были превращены в инструкции VM: strlen, is_int.
  • Использование регистров CPU под регистры VM: IP и FP.
  • Оптимизация функций дублирования и удаления массивов.
  • Использование счетчиков ссылок вместо копирования везде, где можно.
  • PCRE JIT.
  • Оптимизация внутренних функций и serialize().
  • Уменьшение размера кода и обрабатываемых данных.

Одни были очень простыми, например, потребовалось всего три строчки кода, чтобы включить JIT в регулярных перловских выражениях, и это сразу принесло видимое (2-3%) ускорение почти всем приложениям. Другие оптимизации затрагивали какие-то узкие аспекты определенных PHP функций, и не особо интересны, хотя суммарный вклад всех этих мелких усовершенствований вполне значим.

К чему пришли

Это вклад различных подсистем на WordPress/PHP 7.0.

Вклад виртуальной машины увеличен до 50%. Memory Manager потребляет уже меньше 5% — и в основном не за счет оптимизаций самого Memory Manager, а за счет уменьшения количества обращений к нему. Если раньше на этом же тесте память выделялась 130 млн. раз, то сейчас только 10 млн. Может показаться, что все основное ускорение достигнуто за счет уменьшения накладных расходов Memory Manager и уменьшения количества обращений к нему за счет улучшения структур данных, но на самом деле все подсистемы были существенно усовершенствованы.


Основные источники ускорения:

  • Интерпретатор стал работать лучше в 2 раза.
  • Накладные расходы MM уменьшились в 17 раз.
  • Хэш-таблицы стали работать быстрее в 4 раза.
  • Общая производительность на WordPress выросла в 3,5 раза.

В начале статьи мы говорили о 2,5-кратном реальном ускорении, а сейчас цифры другие. Почему так? Дело в том, что реальную скорость мы измеряли в запросах в секунду, а здесь скорость измерена профилировщиком в терминах CPU time, по сути — тактах процессора, когда он не простаивает. Когда PHP ждет ответа от базы данных, процессор стоит и это время здесь не учитывается.

Производительность PHP 7

WordPress 3.6 был для нас основным бенчмарком — на нём мы мониторили производительность с первых дней работы. В какой-то момент, когда из PHP 7 выкинули расширение mysql, нам пришлось его специально поддерживать, просто чтобы продолжить этот график.

На графике видно, что основные прорывы произошли в первые месяцы работы над PHPNG. К августу было набрано 2/3 улучшений. Дальше мы двигались маленькими шажками, и набрали оставшуюся треть.

Разумеется, мы измеряли производительность не только на WordPress, но и на других популярных приложениях, и практически везде мы видим — от 1,5 до 2-кратное ускорение.

PHP 7 и HHVM

По нашей версии мы почти везде обгоняли даже актуальные на тот момент версии HHVM.

Но сравнение со сторонним продуктом — неблагодарное занятие. Всегда выигрыш в пользу измеряющего. Версия команды Facebook показывает другие результаты. На графике HHVM везде пропорционально быстрее. Возможно, это связано с разными процедурами замера, тестированием на разных аппаратных платформах, разницы в тонких настройках, а может повлияли и субъективные факторы.

Апофеоз PHP 7 — начало использования крупными сайтами. Пионерами были китайский Vebia, американский Etsy и Badoo. Highload-проверка вскрыла несколько существенных проблем, но они были быстро локализованы и пофикшены.

Переход на PHP 7.0 для Etsy и Badoo позволил выключить практически половину серверов в веб-фермах. Badoo оценил экономию в миллион долларов.

Показательны графики, что на момент перехода суммарная загрузка процессоров уменьшилась в 2 раза, а потребление памяти — аж в 7 раз.

На этой радостной ноте закончим сегодняшний разговор о PHP 7.0. Но продолжим его в второй части, посвященной PHP 7.1, в оптимизации которого пошли существенно дальше структур данных.

В мае на PHP Russia Дмитрий Стогов выступит с докладом о самых интересных новых технологиях разрабатываемых для PHP 8. Если и ваш опыт во многом связан с PHP, вы знаете, как его правильно готовить, и готовы поделиться своими наработками — присылайте заявки до 1 апреля. И помните, главное, что мы ищем — живой полезный опыт, а с докладом поможем, зададим правильные вопросы и подскажем, куда двигаться.

источник