В ядро PHP 8 добавлен JIT-компилятор, который потенциально может значительно повысить производительность. Однако есть важные оговорки касательно его реального влияния на веб-приложения, поэтому я провёл несколько бенчмарков производительности JIT (все релевантные ссылки я также указал в сносках).

Я также хотел посвятить отдельный пост настройке JIT, так как здесь есть о чём поговорить.

Честно говоря, настройка JIT — один из самых запутанных способов конфигурирования расширений PHP, с которыми я сталкивался. К счастью, есть короткие псевдонимы конфигурации, облегчающие настройку. Тем не менее полезно досконально разобраться в параметрах JIT, так что приступим.

Прежде всего, JIT работает только при включённом opcache. В большинстве установок PHP он включён по умолчанию, но вам следует убедиться, что параметр opcache.enable  установлен в 1 в вашем файле php.ini . Само включение JIT выполняется путем указания opcache.jit_buffer_size  в php.ini . 

Обратите внимание: если вы запускаете PHP из командной строки, вы можете передать эти параметры через флаг -d , а не добавлять их в php.ini :

php -dopcache.enable=1 -dopcache.jit_buffer_size=100M


Если эту директиву опустить, по умолчанию используется значение 0, и JIT не запустится. Если вы тестируете JIT в CLI-скрипте, для включения opcache вам понадобится использовать opcache.enable_cli :

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() :

var_dump(opcache_get_status()['jit']);

Вывод должен выглядеть примерно так:

array:7 [
  "enabled" => true
  "on" => true
  "kind" => 5
  "opt_level" => 4
  "opt_flags" => 6
  "buffer_size" => 4080
  "buffer_free" => 0
]

Если enabled  и on  равны true, всё готово к работе!

Далее, существует несколько способов настройки JIT (и вот здесь начинается путаница с конфигурацией). Вы можете настроить, когда должен запускаться JIT, насколько сильно он должен пытаться оптимизировать код и т. д. Все эти параметры задаются с помощью одного (!) параметра конфигурации: opcache.jit . Это может выглядеть примерно так:

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  | Tracing JIT

R — Распределение регистров

0  | не выполнять распределение регистров 
1  | использовать локальный алгоритм распределения регистров linear-scan 
2  | использовать глобальный алгоритм распределения регистров linear-scan

C — Флаги оптимизации под конкретный ЦП

0  | нет 
1  | включить генерацию инструкций AVX

Один небольшой подвох: в RFC эти параметры перечислены в обратном порядке, поэтому первая цифра представляет значение C , вторая — R  и так далее. Почему нельзя было просто добавить четыре отдельных параметра конфигурации — выше моего понимания, вероятно, чтобы ускорить настройку JIT… правда?

Как бы то ни было, разработчики ядра предлагают 1255  в качестве лучшего значения по умолчанию: это обеспечивает максимальный JIT, использует tracing JIT, глобальный распределитель регистров linear-scan (что бы это ни значало) и включает генерацию инструкций AVX.

Так что ваши настройки ini (или флаги -d ) должны содержать следующие значения:

opcache.enable=1 
opcache.jit_buffer_size=100M
opcache.jit=1255

Кстати, имейте в виду, что opcache.jit  необязателен. Если это свойство опущено, JIT будет использовать значение по умолчанию.

Какое именно значение по умолчанию, спросите вы? Это opcache.jit=tracing .

Постойте, но это же не та странная структура, похожая на битовую маску, которую мы видели ранее? Всё верно: после принятия первоначального RFC авторы признали, что опции, похожие на битовую маску, не слишком удобны для пользователей, поэтому добавили два псевдонима, которые под капотом преобразуются в битовую маску. Это opcache.jit=tracing  и opcache.jit=function .

Разница между ними в том, что function JIT пытается оптимизировать код только в рамках одной функции, в то время как tracing JIT может анализировать весь стек-трейс для поиска и оптимизации «горячего» кода. Разработчики ядра рекомендуют использовать tracing JIT, так как он практически всегда даёт наилучшие результаты. Вы можете прочитать об этих результатах в бенчмарках, которые я проводил. 

Таким образом, единственный параметр, который вам действительно нужно задать для включения JIT с оптимальной конфигурацией, — это opcache.jit_buffer_size , но если вы хотите указать настройки явно, то добавить opcache.jit  также будет неплохой идеей:

opcache.enable=1 
opcache.jit_buffer_size=100M
opcache.jit=tracing