OPcache, JIT и почему PHP не такой уж медленный
Вопрос, где важно не перепутать кэш опкодов, кэш данных и JIT-компиляцию.
OPcache — расширение, которое хранит в разделяемой памяти уже скомпилированные опкоды ваших PHP-файлов, чтобы не компилировать один и тот же код на каждый запрос.
Вопрос 1: «Что такое опкоды и зачем OPcache?»
Что проверяет интервьюер
Понимаете ли вы, что PHP не «читает файл построчно», а работает в два этапа: компиляция исходника в промежуточное представление (опкоды) и исполнение этих опкодов виртуальной машиной Zend. В модели shared-nothing без кэша первый этап повторялся бы для каждого файла на каждый запрос — а это лексический разбор, парсинг и генерация кода для сотен файлов фреймворка.
# посмотреть опкоды файла, не выполняя его
php -d opcache.enable_cli=1 \
-d opcache.opt_debug_level=0x10000 \
script.php
OPcache кладёт результат компиляции в разделяемую память один раз, и все воркеры FPM его переиспользуют. Реальный эффект на типичном фреймворковом приложении — ускорение в 2–3 раза и заметное падение нагрузки на CPU. Это первое, что проверяют на «медленном» проде.
Минимальная боевая конфигурация
; php.ini
opcache.enable=1
opcache.memory_consumption=256 ; МБ под опкоды
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000 ; больше, чем файлов в проекте
opcache.validate_timestamps=0 ; на проде: не проверять mtime файлов
opcache.revalidate_freq=0
opcache.jit_buffer_size=64M
opcache.jit=tracing
Ключевая строка — validate_timestamps=0. С ней PHP перестаёт при каждом обращении проверять время изменения файла, но за это приходится платить: после деплоя кэш надо сбросить самому — перезапуском FPM или вызовом opcache_reset(). На локальной машине значение оставляют равным 1, иначе правки не будут видны.
Вопрос 2: «Что такое preloading?»
Preloading (PHP 7.4) идёт дальше кэша опкодов: указанный скрипт выполняется один раз при старте мастер-процесса и загружает классы в память навсегда — до перезапуска FPM. Такие классы доступны всем запросам без единого обращения к автозагрузчику и файловой системе.
<?php
// preload.php, указанный в opcache.preload
$dir = new RecursiveDirectoryIterator(__DIR__ . '/vendor');
foreach (new RecursiveIteratorIterator($dir) as $file) {
if ($file->getExtension() === 'php') {
opcache_compile_file($file->getPathname());
}
}
Выигрыш — обычно единицы процентов, а цена — обязательный перезапуск FPM после каждого деплоя и невозможность подменить предзагруженные классы. Поэтому честный ответ на собеседовании: «умею настроить, но включаю только когда профилировщик показал, что автозагрузка действительно дорога».
Вопрос 3: «Ускорит ли JIT моё веб-приложение?»
Почти наверняка нет — и это правильный ответ. JIT (PHP 8.0) компилирует горячие опкоды в машинный код прямо во время выполнения. Он даёт кратный прирост на вычислительных задачах: обработке изображений, математике, длинных циклах. Типичное веб-приложение почти всё время ждёт базу, Redis и внешние API, поэтому на нём JIT добавляет считанные проценты, а иногда и ничего.
| Технология | Что кэширует/делает | Где выигрыш |
| OPcache | скомпилированные опкоды файлов | всегда, обязателен на проде |
| Preloading | классы в памяти мастер-процесса | единицы процентов |
| JIT | машинный код горячих участков | математика, циклы, CLI |
| Кэш данных (Redis) | результаты запросов и вычислений | обычно самый большой |
Сильный кандидат добавит: прежде чем спорить о JIT, надо посмотреть в профилировщик (Xdebug, Blackfire, Tideways) и в лог медленных запросов базы. Как правило, 80% времени лежит в N+1-запросах и в отсутствующих индексах, а не в самом PHP.
Типичные ошибки кандидатов
- Считают, что OPcache кэширует результаты работы скрипта или ответы страниц. Он кэширует только опкоды.
- Забывают сбросить кэш после деплоя при
validate_timestamps=0и получают «старый код на проде» — очень частая авария. - Путают OPcache (общий для воркеров кэш опкодов) с APCu (кэш пользовательских данных в разделяемой памяти).
- Обещают «включим JIT и всё полетит» без профилирования.
- Не могут назвать
opcache_get_status()и не знают, как посмотреть заполнение кэша и число промахов.
Как ответить кратко
«PHP компилирует исходник в опкоды и исполняет их на Zend VM. OPcache хранит опкоды в разделяемой памяти, чтобы не компилировать код на каждый запрос — это обязательная настройка прода, ускорение в разы; на проде ставят
validate_timestamps=0и сбрасывают кэш при деплое. Preloading держит классы в памяти мастер-процесса, JIT компилирует горячие участки в машинный код и заметен в основном на вычислительных задачах, а не в вебе, где узкое место — база».