
# Benchmarks

Phasis is **roughly two orders of magnitude slower than V8** on dispatch-bound JavaScript. That ceiling is the inherent cost of a PHP-hosted interpreter vs a native JIT compiler. The question for an embedder isn't "how does it compare to V8" — it's "is it fast enough for what I'm doing." For most embedding workloads the answer is yes.

## When the ceiling doesn't matter

- **Templating** — SSR-style render of a typical page in well under 10 ms.
- **Validation rules** — user-supplied JS for form validation finishes in microseconds.
- **Data transformations** — `array.map(transform)` over hundreds of items completes in tens of milliseconds.
- **Sandbox runners** — short user-supplied scripts with a 1-second host cap leave plenty of headroom.

## When the ceiling matters

- **Compute-heavy inner loops** — image processing, large-array reductions, simulations. Move the compute to a native PHP library and expose it as a [host function](/docs/interop/host-functions).
- **Real-time animation** — anything driving 60 fps. That JS belongs in the client (browser, native app), not the embedded engine.
- **JS reimplementations of compute-expensive algorithms** — cryptography, compression, parsing. PHP has native equivalents that are 1000× faster; use those.

The right mental model: Phasis is a **glue layer** for JS-shaped logic embedded in PHP-shaped applications. The PHP host handles compute; the JS layer handles configuration, branching, and small transformations.

## What makes the engine fast (relative to a naïve tree-walker)

- **Bytecode VM** — most hot dispatch runs through `VM::execute`'s opcode switch, not the AST.
- **Custom callstack** — every JS call keeps state inside `VM::execute` and saves the caller's frame onto a pool; no new PHP frame per JS call. See [Bytecode VM § Custom callstack](/docs/advanced/bytecode-vm).
- **Inline caches** — a per-PC monomorphic cache makes prototype-method-on-instance reads (the `c.method()` shape) skip the prototype-chain walk.
- **Frame-snapshot generators** — simple generator bodies suspend by capturing the dispatch state into a heap object instead of crossing a PHP Fiber.
- **JsToPhp transpiler** — numeric / locals-heavy bodies (including `obj.method()`, `new Ctor()`, and `this.member` reads) compile to PHP closures that PHP 8.5's tracing JIT lowers to native machine code.
- **JSON.stringify cache** — `JSON.stringify(o)` caches its output on the input object with transitive version invalidation, accelerating the deep-clone idiom `JSON.parse(JSON.stringify(o))`.

## Measuring your own workload

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

This runs the microbench suite shipped under `bench/microbench.js` — a handful of synthetic workloads (loop arithmetic, recursive functions, object creation, prototype-method calls, array push, JSON round-trip, etc.) and prints median wall time per test. Useful as a sanity check; current numbers are committed to [`BENCH.md`](https://github.com/inline0/phasis/blob/main/BENCH.md) on every CI bench run.

For your own code, swap in any script:

```bash
php -d xdebug.mode=off bench/run.php path/to/your-script.js
```

For PHP-level profiling, Xdebug's profiler works against Phasis exactly like any other PHP application. Run a script under `xdebug.mode=profile` and inspect the cachegrind output to see which `Phasis\` classes dominate.

## Roadmap

What would close more of the V8 gap, in rough order of tractability:

- **Polymorphic inline caches** — let a single `LOAD_MEMBER` call site hold 2-4 cached shapes before falling back to slow dispatch. Helps code that processes heterogeneous receivers.
- **`yield*` and try-with-yield on the frame-snapshot generator path** — these shapes currently route to the Fiber-backed tree-walker. Each needs explicit state-machine handling (delegate-iterator forwarding, handler stack preserved across suspend) before they're snapshot-safe.
- **Async generators and `await` on snapshots** — the spec composes generator suspension with the microtask queue. Once the bytecode body supports both `yield` and `await`, async generators become snapshot-friendly too.
- **Parameter type inference in JsToPhp** — function parameters default to numeric type, so `function (obj) { obj.method() }` bails the compiler. Inferring object-typed parameters from how they're used in the body would unlock method calls on function arguments without requiring the receiver to be locally constructed via `new`.
- **Direct opcache emission** — skip JsToPhp's `eval()` step by writing opcache-format opcodes directly. Deepest item; the others can be done within the existing architecture.

None of these are necessary for the embedding workloads Phasis targets.
