Бенчмарки
Phasis примерно в 100 раз медленнее V8 при нагрузках, ограниченных диспетчеризацией JavaScript. Такова неизбежная цена интерпретатора на PHP по сравнению с нативным JIT-компилятором. Для встраивания важно не то, «как он выглядит на фоне V8», а то, «достаточно ли он быстр для моей задачи». Для большинства сценариев встраивания — да.
Когда это ограничение несущественно
- Шаблонизация — серверный рендеринг типичной страницы в стиле SSR занимает значительно меньше 10 ms.
- Правила проверки — проверка предоставленного пользователем JS для форм занимает микросекунды.
- Преобразование данных —
array.map(transform)для сотен элементов выполняется за десятки миллисекунд. - Изолированный запуск — короткие скрипты пользователя с ограничением хоста в 1 секунду оставляют большой запас.
Когда это ограничение существенно
- Вычислительно затратные внутренние циклы — обработка изображений, свертки больших массивов, симуляции. Перенесите вычисления в нативную библиотеку PHP и предоставьте ее как функцию хоста.
- Анимация в реальном времени — все, что должно обеспечивать 60 fps. Такую JS лучше реализовать на клиенте (в браузере или нативном приложении), а не во встроенном движке.
- Реализации на JS алгоритмов с высокой вычислительной сложностью — криптография, сжатие, разбор данных. У PHP есть нативные аналоги, которые быстрее в 1000 раз; используйте их.
Правильная модель: Phasis — это связующий слой для логики в стиле JS, встроенной в приложения в стиле PHP. Хост на PHP выполняет вычисления, а слой JS отвечает за настройку, ветвление и простые преобразования.
За счет чего движок быстрее (по сравнению с наивным обходчиком дерева)
- Виртуальная машина байткода — большая часть горячей диспетчеризации проходит через переключатель кодов операций
VM::execute, а не через AST. - Пользовательский стек вызовов — состояние каждого вызова JS сохраняется в
VM::execute, а кадр вызывающего кода помещается в пул; для каждого вызова JS не создается новый кадр PHP. См. Виртуальная машина байткода § Пользовательский стек вызовов. - Встроенные кэши — мономорфный кэш для каждой точки выполнения позволяет пропустить обход цепочки прототипов при чтении метода прототипа экземпляра (шаблон
c.method()). - Генераторы со снимком кадра — простые тела генераторов приостанавливаются, сохраняя состояние диспетчеризации в объекте кучи вместо перехода через PHP Fiber.
- Транспилятор JsToPhp — тела с большим количеством операций над числами и локальными переменными (включая обращения к
obj.method(),new Ctor()иthis.member) компилируются в замыкания PHP, которые трассирующий JIT-компилятор PHP 8.5 преобразует в машинный код. - Кэш JSON.stringify —
JSON.stringify(o)сохраняет результат на входном объекте и транзитивно инвалидирует его при изменении версии, ускоряя идиому глубокого копированияJSON.parse(JSON.stringify(o)).
Измерение производительности на собственных задачах
php bench/run.php
Этот набор микротестов поставляется в bench/microbench.js. В нем есть несколько синтетических нагрузок (арифметика в циклах, рекурсивные функции, создание объектов, вызовы методов прототипа, добавление элементов в массив, обмен с JSON и т. д.); он выводит медианное время выполнения каждого теста. Это полезная проверка на здравый смысл; текущие показатели фиксируются в BENCH.md при каждом запуске тестов производительности в CI.
Для проверки собственного кода подставьте любой скрипт:
php -d xdebug.mode=off bench/run.php path/to/your-script.js
Для профилирования на уровне PHP профилировщик Xdebug работает с Phasis так же, как с любым другим приложением PHP. Запустите скрипт с помощью xdebug.mode=profile и изучите вывод cachegrind, чтобы увидеть, какие классы Phasis\ занимают больше всего ресурсов.
Дорожная карта
Что позволит сократить отставание V8; в примерном порядке сложности реализации:
- Полиморфные встроенные кэши — позволят одной точке вызова
LOAD_MEMBERхранить кэш для 2–4 типов перед переходом к медленной диспетчеризации. Это поможет при обработке объектов разных типов. yield*и try-with-yield на пути генератора со снимком кадра — сейчас этот код выполняется обходчиком дерева на основе Fiber. Для поддержки снимков нужна явная обработка конечного автомата: передача управления делегированному итератору и сохранение стека обработчиков при приостановке.- Асинхронные генераторы и
awaitв снимках — спецификация связывает приостановку генератора с очередью микрозадач. Когда тело байткода будет поддерживать иyield, иawait, асинхронные генераторы тоже можно будет выполнять со снимком. - Вывод типов параметров в JsToPhp — параметры функций по умолчанию имеют числовой тип, поэтому компилятор прекращает работу при
function (obj) { obj.method() }. Вывод типов объектов по их использованию в теле функции позволит вызывать методы для аргументов без необходимости создавать принимающий объект локально черезnew. - Прямая генерация opcache — пропуск этапа
eval()в JsToPhp за счет непосредственной записи кодов операций в формате opcache. Это самая сложная задача; остальные можно выполнить в рамках текущей архитектуры.
Ни одно из этих улучшений не требуется для сценариев встраивания, на которые рассчитан Phasis.