
# Бенчмарки

Phasis примерно в **100 раз медленнее V8** при нагрузках, ограниченных диспетчеризацией JavaScript. Такова неизбежная цена интерпретатора на PHP по сравнению с нативным JIT-компилятором. Для встраивания важно не то, «как он выглядит на фоне V8», а то, «достаточно ли он быстр для моей задачи». Для большинства сценариев встраивания — да.

## Когда это ограничение несущественно

- **Шаблонизация** — серверный рендеринг типичной страницы в стиле SSR занимает значительно меньше 10 ms.
- **Правила проверки** — проверка предоставленного пользователем JS для форм занимает микросекунды.
- **Преобразование данных** — `array.map(transform)` для сотен элементов выполняется за десятки миллисекунд.
- **Изолированный запуск** — короткие скрипты пользователя с ограничением хоста в 1 секунду оставляют большой запас.

## Когда это ограничение существенно

- **Вычислительно затратные внутренние циклы** — обработка изображений, свертки больших массивов, симуляции. Перенесите вычисления в нативную библиотеку PHP и предоставьте ее как [функцию хоста](https://phasis.inline0.dev/ru/docs/interop/host-functions).
- **Анимация в реальном времени** — все, что должно обеспечивать 60 fps. Такую JS лучше реализовать на клиенте (в браузере или нативном приложении), а не во встроенном движке.
- **Реализации на JS алгоритмов с высокой вычислительной сложностью** — криптография, сжатие, разбор данных. У PHP есть нативные аналоги, которые быстрее в 1000 раз; используйте их.

Правильная модель: Phasis — это **связующий слой** для логики в стиле JS, встроенной в приложения в стиле PHP. Хост на PHP выполняет вычисления, а слой JS отвечает за настройку, ветвление и простые преобразования.

## За счет чего движок быстрее (по сравнению с наивным обходчиком дерева)

- **Виртуальная машина байткода** — большая часть горячей диспетчеризации проходит через переключатель кодов операций `VM::execute`, а не через AST.
- **Пользовательский стек вызовов** — состояние каждого вызова JS сохраняется в `VM::execute`, а кадр вызывающего кода помещается в пул; для каждого вызова JS не создается новый кадр PHP. См. [Виртуальная машина байткода § Пользовательский стек вызовов](https://phasis.inline0.dev/ru/docs/advanced/bytecode-vm).
- **Встроенные кэши** — мономорфный кэш для каждой точки выполнения позволяет пропустить обход цепочки прототипов при чтении метода прототипа экземпляра (шаблон `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))`.

## Измерение производительности на собственных задачах

```bash
php bench/run.php
```

Этот набор микротестов поставляется в `bench/microbench.js`. В нем есть несколько синтетических нагрузок (арифметика в циклах, рекурсивные функции, создание объектов, вызовы методов прототипа, добавление элементов в массив, обмен с JSON и т. д.); он выводит медианное время выполнения каждого теста. Это полезная проверка на здравый смысл; текущие показатели фиксируются в [`BENCH.md`](https://github.com/inline0/phasis/blob/main/BENCH.md) при каждом запуске тестов производительности в CI.

Для проверки собственного кода подставьте любой скрипт:

```bash
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.
