Предварительная загрузка в PHP 7.4

Автор оригинала: Brent
  • Перевод
Перевод данной статьи подготовлен специально для студентов курса «Backend разработчик на PHP».



В PHP 7.4 добавлена предварительная загрузка — возможность, которая позволяет значительно повысить производительность кода.

О предзагрузке в двух словах.

  • Для предварительной загрузки файлов вам потребуется написать отдельный PHP-скрипт.
  • Этот скрипт выполняется однократно при запуске сервера.
  • Все предварительно загруженные файлы доступны в памяти для всех запросов.
  • Изменения, внесенные в исходный файл, не подействуют, пока вы не перезапустите сервер.

Поговорим о новой возможности подробнее.


Больше, чем Opcache


Да, предварительная загрузка основывается на opcache, но это не совсем одно и то же. Opcache берет исходные файлы PHP, компилирует их в опкоды, после чего сохраняет скомпилированные файлы на диск.

Опкоды можно считать низкоуровневым представлением вашего кода, которое легко интерпретируется во время выполнения. Таким образом, opcache позволяет пропустить этап трансляции исходных файлов в то, что, собственно, и необходимо интерпретатору PHP во время выполнения. Заметная экономия!

Тем не менее можно сэкономить еще больше. Скомпилированные при помощи opcash файлы ничего не знают о других файлах. Если у вас есть класс А, являющийся расширением класса B, их все равно нужно будет связать во время выполнения. Кроме того, opcache проверяет, изменились ли исходные файлы, и при обнаружения изменений аннулирует их кэши.

И тут на помощь приходит предварительная загрузка: она не только компилирует исходные файлы в опкоды, но и связывает зависимые классы, трейты и интерфейсы. Она сохраняет такой «скомпилированный» фрагмент исполняемого кода (то есть кода, который может использовать PHP-интерпретатор) в памяти.

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

О каких же «частях кодовой базы» идет речь?


Предварительная загрузка на практике


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

Ничего сложного.

  • Вы предоставляете скрипт предварительной загрузки и даете ссылку на него в вашем файле php.ini с помощью opcache.preload.
  • Каждый PHP-файл, который вы хотите предварительно загрузить, нужно передать в opcache_compile_file() из скрипта предварительной загрузки.

Допустим, вы хотите предварительно загрузить какой-нибудь фреймворк. Пусть это будет Laravel. В этом случае ваш скрипт должен просмотреть все PHP-файлы в директории vendor/laravel и добавить их поочередно.

Вот как вы можете подключить этот скрипт в php.ini:

opcache.preload=/path/to/project/preload.php


А вот пример реализации:

$files = /* Массив файлов, которые вы хотите предварительно загрузить */;
 
foreach ($files as $file) {
    opcache_compile_file($file);
}

Вместо opcache_compile_file вы можете использовать include. Однако, похоже, здесь не обошлось без бага, так как на момент написания статьи второй вариант не сработал.

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


Появилось предупреждение Can't preload unlinked class? Дело в том, что перед предзагрузкой файлов нужно также предварительно загрузить их зависимые объекты — интерфейсы, трейты и родительские классы.

Если возникнут какие-либо проблемы с зависимостями классов, то вас предупредят об этом при запуске сервера:

Can't preload unlinked class 
Illuminate\Database\Query\JoinClause: 
Unknown parent 
Illuminate\Database\Query\Builder

Обратите внимание, что opcache_compile_file() только распарсит файл, но не выполнит его. Это значит, что если у класса есть зависимости, которые не были предварительно загружены, то и сам класс не может быть предварительно загружен.

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

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

Поддержка composer


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

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

Требования к серверу


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

Вы уже знаете, что нужно создать запись в php.ini, чтобы предварительная загрузка сработала. Это значит, что если вы используете shared-хостинг, то не сможете настраивать PHP как вздумается.

На практике вам понадобится выделенный (виртуальный) сервер, чтобы оптимизировать предварительно загруженные файлы для отдельного проекта. Имейте это в виду.

Также помните, что вам нужно будет перезапускать сервер (если вы используете php-fpm, этого достаточно) каждый раз, когда вы захотите перезагрузить файлы в памяти. Для большинства это очевидно, но не будет лишним напомнить.

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


Теперь перейдем к самому важному вопросу: действительно ли предварительная загрузка повышает производительность?

Разумеется! Бен Морел (Ben Morel) поделился результатами сравнительного тестирования, которые можно найти в той же теме по composer, на которую мы ссылались выше.

И еще, что интересно. При желании вы можете предварительно загрузить только так называемые hot classes — классы, которые часто используются в вашей кодовой базе. Тесты Бена Морела показывают, что загрузка всего около 100 таких классов обеспечивает более высокий рост производительности, чем предварительная загрузка всего сразу. В первом случае производительность повышается на 17 %, во втором — на 13 %.

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

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

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



Будете ли вы использовать предварительную загрузку в новой версии PHP 7.4? Появились мысли или замечания? Напишите мне в Twitter или по электронной почте.

Традиционно ждем ваши комментарии и плюсы, если считаете статью интересной :-)
OTUS. Онлайн-образование
762,20
Цифровые навыки от ведущих экспертов
Поделиться публикацией

Комментарии 16

    +1
    Ждем когда в докер композе можно будет сделать `rm -rf src/`и этот образ залить в проде через докер хаб
      0
      opcache проверяет, изменились ли исходные файлы, и при обнаружения изменений аннулирует их кэши.
      Не обязательно, в настройках opcache.validate_timestamps как раз для управления этим поведением.
        +1
        Но если у нас есть конкретный список файлов, почему нельзя было сразу сделать автоматическую их перезагрузку при изменении кода?
        Ведь по сути нет разницы, сделает это сам сервер по watch(files) или мы его вручную прибьем.

        Иначе теряется вся суть PHP (оживление и умирание при запросе).
          0
          PHP ничего не отслеживает при изменении файлов, просто если стоит opcache.validate_timestamps, то при попытке использовать файл, php обратиться к диску и сверит дату изменения файла. То есть это дополнительное время на медленную io-операцию.
          Preload фактически расширяет возможность не сбрасывать кеш опкеша, позволяя это для конкретных файлов (как часто у вас на продакшн меняется содержимое папки vendor? да даже на дев), в то время как до этого opcache.validate_timestamps кешировал намертво всё.

          И суть умирания после запроса не только в этом, а больше в изоляции запросов друг от друга.
            0
            Preload фактически расширяет возможность не сбрасывать кеш опкеша, позволяя это для конкретных файлов (как часто у вас на продакшн меняется содержимое папки vendor? да даже на дев), в то время как до этого opcache.validate_timestamps кешировал намертво всё.
            opcache намертво не кэшировал, кэш можно было сбрасывать как для всех файлов opcache_reset так и для отдельных opcache_invalidate.
          0

          Как настроить разные opcache.preload для разных виртуальных хостов Apache или Nginx, использующих одинаковую версию PHP?

            +1

            разнести по разным fpm пулам?

            0
            Небольшая неточность. Не 13 и 17%, а 13 и 16%.
            А по теме — хорошая штука.
            Хотя прирост конечно не такой как между 5.6 vs 7.3 :)
            8.0 pho кроме JIT чем то обещает ещё удивить нас?
              0
              > Хотя прирост конечно не такой как между 5.6 vs 7.3 :)

              очень честное сравнение одной минорной версии против мажорной и трех минорных

              > 8.0 pho кроме JIT чем то обещает ещё удивить нас?

              как будто этого мало. Если добавить еще что-то, то
              1) не известно когда будет релиз. А не как сейчас нормальное ежегодное инкрементальное обновление.
              2) Учитывая сколько надо перелапатить ради джита — добавление любой фичи еще увеличивает вероятность багов експоненциально.
              3) Джит, ИМХО, важен не ускорением а ффи. Который открывает новую главу в экосистеме.
              –2
              Я так понимаю это перевод какой то статьи? Вопросы тут не уместны?
              А то как бы:
              «Допустим, вы хотите предварительно загрузить какой-нибудь фреймворк. Пусть это будет Laravel. В этом случае ваш скрипт должен просмотреть все PHP-файлы в директории vendor/laravel и добавить их поочередно.»
              Но пример реализации потом не для Laravel показан.
                0

                Какая разница? Там пример на показать как это вообще работает.

                0
                Если класс загружается в память — интересно, будут ли статичекие свойства сохранятся между запросами?
                Если да — то можно было бы реализовать какой то DatabaseConnectionPoolManager. Давно мечтаю о подобном
                  +2
                  Посмотрите на swoole или spiral/roadrunner — то, что вам хочется. Уже сейчас :)
                    0
                    Спасибо за наводку!
                    +1
                    Есть еще amphp и reactphp — и то и другое позволяет делать постоянно запущенные, асинхронно работающие приложения на PHP. Первый немного активнее развивается по-моему (больше асинхронных оберток под разные БД и прочее). Они могут части друг-друга использовать (через адаптеры). Это уже большой новый мир, много пакетов и все отлично работает (особенно если думать про возможные утечки памяти и следить за этим).

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

                  Самое читаемое