Для чего нужен файл preload.php в PHP?

Ответ

Файл preload.php — это скрипт, используемый в связке с OPcache (начиная с PHP 7.4) для механизма предзагрузки (preloading). Его цель — скомпилировать и загрузить в память OPcache определенный набор PHP-файлов (классы, функции, скрипты) еще до запуска любого приложения на этом экземпляре PHP-FPM. Это превращает их из байт-кода в готовые к исполнению структуры данных в общей памяти, что исключает этап компиляции и автозагрузки при каждом запросе.

Какую проблему это решает на практике? В высоконагруженном Symfony/Laravel приложении каждый запрос тратит время на загрузку фреймворка, его компонентов и основных классов приложения через Composer autoload. Preloading делает это один раз при старте PHP-FPM.

Пример preload.php для Symfony-приложения:

<?php
// preload.php
if (!function_exists('opcache_compile_file')) {
    return; // Выходим, если OPcache не активирован
}

// 1. Предзагрузка ядра фреймворка и Composer autoloader
$vendorPath = __DIR__ . '/vendor/';
opcache_compile_file($vendorPath . 'autoload.php');

// 2. Предзагрузка часто используемых компонентов Symfony
$symfonyFiles = [
    $vendorPath . 'symfony/http-foundation/Request.php',
    $vendorPath . 'symfony/http-kernel/HttpKernel.php',
    $vendorPath . 'symfony/dependency-injection/Container.php',
];
foreach ($symfonyFiles as $file) {
    if (file_exists($file)) {
        opcache_compile_file($file);
    }
}

// 3. Предзагрузка собственных классов приложения (например, всех Entity и основных Services)
$appFiles = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . '/src/'));
foreach ($appFiles as $file) {
    if ($file->isFile() && $file->getExtension() === 'php') {
        opcache_compile_file($file->getRealPath());
    }
}

Конфигурация в php.ini:

opcache.enable=1
opcache.preload=/var/www/myapp/preload.php
opcache.preload_user=www-data

Важные нюансы из опыта:

  • Потребление памяти: Все предзагруженные классы остаются в памяти OPcache до его перезапуска. Необходимо тщательно подбирать файлы для preload, чтобы не исчерпать память.
  • Инвалидация кэша: При обновлении кода предзагруженных файлов необходимо перезапустить PHP-FPM (или сервер), чтобы изменения вступили в силу, так как OPcache не перезагрузит эти файлы автоматически.
  • Динамические классы: Классы, имена которых определяются во время выполнения (например, через переменные), не могут быть предзагружены. Preloading наиболее эффективен для стабильного, часто используемого кода ядра приложения.

В результате правильной настройки preloading давал мне прирост производительности на 10-30% для API-endpoints, сильно зависящих от времени инициализации фреймворка.

Ответ 18+ 🔞

Слушай, а вот эта штука с preload.php — это вообще огонь, если ты на PHP 7.4+ и с OPcache. Представь себе: ты каждый раз при запросе грузишь всю эту хуйню — фреймворк, автолоадер, сотни классов. А можно один раз, при старте PHP-FPM, взять и засунуть всё это в память, уже скомпилированное. И потом просто исполнять, без лишней ебалы с компиляцией и автозагрузкой. Красота же!

Ну и нахуя это надо, спросишь? А нахуя — чтобы не тратить время на одно и то же при каждом запросе в твоём Symfony или Laravel. Особенно если нагрузка овердохуища. Всё уже готово лежит, бери и работай.

Вот смотри, как это примерно выглядит в preload.php:

<?php
// preload.php
if (!function_exists('opcache_compile_file')) {
    return; // Если OPcache выключен — иди нахуй, нечего тут делать
}

// 1. Автолоадер Composer — основа основ, его первым делом
$vendorPath = __DIR__ . '/vendor/';
opcache_compile_file($vendorPath . 'autoload.php');

// 2. Часто используемые файлы Symfony, без них никуда
$symfonyFiles = [
    $vendorPath . 'symfony/http-foundation/Request.php',
    $vendorPath . 'symfony/http-kernel/HttpKernel.php',
    $vendorPath . 'symfony/dependency-injection/Container.php',
];
foreach ($symfonyFiles as $file) {
    if (file_exists($file)) {
        opcache_compile_file($file);
    }
}

// 3. Свой собственный код из /src — все Entity, Services и прочую требуху
$appFiles = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . '/src/'));
foreach ($appFiles as $file) {
    if ($file->isFile() && $file->getExtension() === 'php') {
        opcache_compile_file($file->getRealPath());
    }
}

А в php.ini надо вот такой магический пендель добавить:

opcache.enable=1
opcache.preload=/var/www/myapp/preload.php
opcache.preload_user=www-data

Но, чувак, есть нюансы, ёпта! Без них никуда:

  • Память жрёт как не в себя. Всё, что ты предзагрузил, будет висеть в памяти до перезапуска PHP-FPM. Так что не надо туда всю свою кодобазу пихать, а то накроется медным тазом твой сервак. Выбирай только самое ядро, самое частое.
  • Кэш не обновится сам. Поменял код в предзагруженном файле? Перезапускай PHP-FPM, иначе нихуя не увидишь. OPcache такую срань сам не перечитает.
  • С динамическими именами классов — нихуя не выйдет. Если класс создаётся через $className = 'App\' . $name;, то его предзагрузить нельзя. Только статичный, нормальный код.

Если всё правильно настроить, то прирост на API, которые сильно зависят от инициализации фреймворка, может быть 10-30%. Не хило, да? Главное — не переборщить, а то будет тебе хиросима вместо оптимизации.