PHP 8 добавляет JIT-компилятор в ядро PHP, который потенциально может значительно ускорить производительность.
Настройка JIT - это один из самых запутанных способов настройки PHP-расширения. К счастью, есть несколько сокращений конфигурации, чтобы ее было проще настроить. Тем не менее, полезно знать о конфигурации JIT в деталях, так что приступим.
Прежде всего, JIT будет работать, только если включен opcache, что является стандартом для большинства PHP, но вы должны убедиться, что opcache.enable установлен в 1 в вашем файлеphp.ini. Включение самого JIT осуществляется путем указания opcache.jit_buffer_size в php.ini.
Обратите внимание, что если вы запускаете PHP через командную строку, вы также можете передать эти опции через флаг -d, вместо того, чтобы добавлять их в php.ini:
1 | php -dopcache.enable=1 -dopcache.jit_buffer_size=100M |
Если эта директива исключена, значение по умолчанию будет равно 0, и JIT не будет запущен. Если вы тестируете JIT в сценарии CLI, вам нужно использовать opcache.enable_cli, чтобы включить opcache:
1 | php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=100M |
Разница между opcache.enable и opcache.enable_cli заключается в том, что первый вариант следует использовать, если вы используете, например, встроенный сервер PHP. Если же вы запускаете скрипт CLI, вам понадобится opcache.enable_cli.
Прежде чем продолжить, давайте убедимся, что JIT действительно работает, создадим PHP-скрипт, доступный через браузер или CLI (в зависимости от того, где вы тестируете JIT), и посмотрим на вывод opcache_get_status():
1 | var_dump(opcache_get_status()['jit']); |
На выходе должно получиться что-то вроде этого:
1 2 3 4 5 6 7 8 9 | array:7 [ "enabled" => true "on" => true "kind" => 5 "opt_level" => 4 "opt_flags" => 6 "buffer_size" => 4080 "buffer_free" => 0 ] |
Если значения enabled и on верны, вы готовы к работе!
Далее, есть несколько способов настроить JIT (и здесь мы попадем в путаницу конфигурации). Вы можете настроить, когда JIT должен запускаться, сколько он должен пытаться оптимизировать и т.д. Все эти параметры настраиваются с помощью одной (!) конфигурационной записи: opcache.jit. Она может выглядеть примерно так:
1 2 | opcache.enable=1 opcache.jit=1255 |
Итак, что означает это число? В RFC перечислены значения каждого из них. Имейте в виду: это не битовая маска, каждое число просто представляет другую опцию конфигурации. В RFC перечислены следующие параметры:
O - уровень оптимизации
- 0 не использовать JIT
- 1 минимальный JIT (вызов стандартных VM-обработчиков)
- 2 выборочная инкрустация VM-обработчиков
- 3 оптимизированный JIT, основанный на статическом определении типа отдельной функции
- 4 оптимизированный JIT на основе статического определения типа и дерева вызовов
- 5 оптимизированный JIT на основе статического определения типа и анализа внутренних процедур
T - JIT-триггер
- 0 JIT все функции при первой загрузке скрипта
- 1 JIT-функция при первом выполнении
- 2 Профилирование по первому запросу и компиляция горячих функций по второму запросу
- 3 Профилирование на лету и компиляция горячих функций
- 4 Компиляция функций с тегом @jit в doc-комментариях
- 5 Трассировка JIT
R - распределение регистров
- 0 не выполнять распределение регистров
- 1 использовать локальный распределитель регистров линейного сканирования
- 2 использовать глобальный распределитель регистров линейного сканирования
C - флаги оптимизации для конкретного процессора
- 0 нет
- 1 включить генерацию инструкций AVX
Одна маленькая загвоздка: RFC перечисляет эти опции в обратном порядке, поэтому первая цифра представляет значение C, вторая - R, и так далее. Почему просто не было добавлено четыре конфигурационные записи, вероятно, чтобы сделать настройку JIT быстрее... верно?
В любом случае, internals предлагает 1255 в качестве лучшего по умолчанию, он будет делать максимальный джиттинг, использовать трассировку JIT, использовать глобальный распределитель регистров liner-scan - каким бы он ни был - и включает генерацию инструкций AVX.
Поэтому ваши ini-настройки (или флаги -d) должны иметь такие значения:
1 2 3 | opcache.enable=1 opcache.jit_buffer_size=100M opcache.jit=1255 |
Помните, что opcache.jit является необязательным свойством. JIT будет использовать значение по умолчанию, если это свойство опущено.
Какое значение по умолчанию, спросите вы? Это opcache.jit=tracing.
Постойте, это не та странная структура, похожая на битмаску, которую мы видели ранее? Все верно: после того, как оригинальный RFC был принят, интерналы поняли, что опции, похожие на битовые маски, не слишком удобны для пользователя, поэтому они добавили два псевдонима, которые под капотом переводятся в битовые маски. Это opcache.jit=tracing и opcache.jit=function.
Разница между ними в том, что функциональный JIT будет пытаться оптимизировать код только в пределах одной функции, в то время как трассирующий JIT может просматривать всю трассировку стека для выявления и оптимизации горячего кода. Internals рекомендует использовать трассирующий JIT, потому что он, вероятно, почти всегда дает лучшие результаты.
Таким образом, единственным параметром, который вам действительно нужно установить, чтобы включить JIT с его оптимальной конфигурацией, является opcache.jit_buffer_size, но если вы хотите быть явным, то перечислить opcache.jit будет не такой уж плохой идеей:
1 2 3 | opcache.enable=1 opcache.jit_buffer_size=100M opcache.jit=tracing |