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

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

Вкратце, вот как это работает:

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

Давайте посмотрим на это подробно.

Почти Opcache, но немного больше

Хотя предварительная загрузка построена поверх opcache, это не совсем то же самое. Opcache возьмет ваши исходные файлы PHP, скомпилирует их в «opcode» и сохранит эти скомпилированные файлы на диске.

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

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

Здесь и начинается предварительная загрузка: она не только компилирует исходные файлы в “opcode”, но также связывает вместе связанные классы, трейты и интерфейсы. Затем он сохранит этот «скомпилированный» двоичный объект исполняемого кода, то есть кода, используемого интерпретатором PHP, в памяти.

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

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

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

Правила просты:

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

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

Пример php.ini:

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

Примерная реализация:

$files = /* An array of files you want to preload */;foreach ($files as $file) {
opcache_compile_file($file);
}

Зависимость классов при подключении

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

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

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

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

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

К счастью, есть способ гарантировать, что связанные файлы также загружаются: вместо использования opcache_compile_fileвы можете использовать require_onceи позволить зарегистрированному автозагрузчику (например, композеру) позаботиться обо всем остальном.

$files = /* All files in eg. vendor/laravel */;foreach ($files as $file) {
require_once($file);
}

Еще пара моментов.

Если вы попытаетесь предварительно загрузить Laravel, там есть некоторые классы, которые имеют зависимости от других классов, но эти зависимости формируются в рантайме. В результате, вы можете столкнуться с ошибками “class not found”, пытаясь предварительно загрузить все. К счастью, в стандартной установке Laravel есть только несколько этих классов, которые можно легко игнорировать. Для удобства я написал небольшой класс preloader, чтобы упростить игнорирование файлов.

Добавив этот класс в тот же сценарий предварительной загрузки, мы теперь можем загрузить всю инфраструктуру Laravel следующим образом:

// …(new Preloader())
->paths(__DIR__ . '/vendor/laravel')
->ignore(
\Illuminate\Filesystem\Cache::class,
\Illuminate\Log\LogManager::class,
\Illuminate\Http\Testing\File::class,
\Illuminate\Http\UploadedFile::class,
\Illuminate\Support\Carbon::class,
)
->load();

Это работает?

Это, конечно, самый важный вопрос: все ли файлы были загружены правильно? Вы можете просто протестировать его, перезапустив сервер, и вывести opcache_get_status()в виде скрипта PHP. Вы увидите, что у него есть ключ preload_statistics, который будет перечислять все предварительно загруженные функции, классы и скрипты; а также память, занятая предварительно загруженными файлами.

Поддержка композера

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

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

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

Вы уже знаете, что вам нужно указать запись в php.ini для предварительной загрузки для работы. Это означает, что если вы используете виртуальный хостинг, вы не сможете настраивать PHP так, как вам хочется. На практике вам понадобится выделенный (виртуальный) сервер, чтобы иметь возможность оптимизировать предварительно загруженные файлы для одного проекта. Так что имейте это в виду.

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

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

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

Ответ, да: Бен Морел (автор этой статьи) поделился некоторыми результатами, которые можно найти здесь.

Полученные результаты

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

  • + 13% при предварительной загрузке всех файлов
  • + 16% при предварительной загрузке только основных классов

Вы можете предварительно загружать только «основные классы» — классы, которые часто используются в вашей кодовой базе. Тесты Бена показывают, что загрузка только около 100 основных классов на самом деле дает лучший прирост производительности, чем предварительная загрузка всего. Это разница в увеличении производительности на 13% и 17%.

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

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

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

Будете ли вы использовать предварительную загрузку после выхода PHP 7.4?

Habr: Пробуем preload (PHP 7.4) и RoadRunner

--

--