基准测试
在受调度开销限制的 JavaScript 中,Phasis 的速度大约比 V8 慢两个数量级。这一上限源于托管在 PHP 中的解释器与本机 JIT 编译器之间固有的开销差异。对嵌入者来说,问题不是“它与 V8 相比如何”,而是“它对我的用途来说够快吗”。对于大多数嵌入式工作负载,答案是肯定的。
何时性能上限无关紧要
- 模板渲染——典型页面的 SSR 风格渲染远低于 10 ms。
- 验证规则——用户提供的 JS 表单验证可在微秒内完成。
- 数据转换——对数百个项目执行
array.map(transform)可在几十毫秒内完成。 - 沙箱运行器——短小的用户脚本即使受到宿主 1 秒的时间限制,仍有充足余量。
何时性能上限会造成影响
- 计算密集型内层循环——图像处理、大型数组归约、模拟。将计算移至本机 PHP 库,并将其作为 宿主函数公开。
- 实时动画——任何驱动 60 fps 的内容。此类 JS 应放在客户端(浏览器、本机应用)中,而不是嵌入式引擎里。
- 用 JS 重写计算密集型算法——加密、压缩、解析。PHP 有速度快 1000× 的本机实现;请使用这些实现。
正确的理解方式是:Phasis 是一层胶水层,用于在 PHP 形态的应用中嵌入 JS 形态的逻辑。PHP 宿主负责计算;JS 层负责配置、分支和小规模转换。
引擎相较于朴素树遍历器的速度优势来自哪里
- 字节码 VM — 大多数热点分发都经过
VM::execute的操作码 switch,而不是 AST。 - 自定义调用栈 — 每次 JS 调用都会将状态保存在
VM::execute中,并把调用方的帧存入池中;每次 JS 调用不会新建 PHP 帧。参见 自定义调用栈(字节码 VM)。 - 内联缓存 — 每个 PC 都有单态缓存,使原型方法在实例上的读取(
c.method()形态)跳过原型链遍历。 - 帧快照生成器 — 简单的生成器函数体通过将分发状态捕获到堆对象中来挂起,无需跨越 PHP Fiber。
- JsToPhp 转译器 — 数值运算和局部变量密集型函数体(包括
obj.method()、new Ctor()和this.member读取)会编译成 PHP 闭包,再由 PHP 8.5 的跟踪 JIT 降为本机机器码。 - JSON.stringify 缓存 —
JSON.stringify(o)会在输入对象上缓存输出,并通过传递式版本失效机制加速深克隆惯用写法JSON.parse(JSON.stringify(o))。
测量你自己的工作负载
命令行
php bench/run.php
这会运行 bench/microbench.js 随附的微基准测试套件——其中包含少量合成工作负载(循环算术、递归函数、对象创建、原型方法调用、数组 push、JSON 往返等),并打印每项测试的中位墙钟时间。可用于基本 sanity check;每次 CI 基准测试运行的当前数据都会提交到 BENCH.md。
对于你自己的代码,替换为任意脚本:
命令行
php -d xdebug.mode=off bench/run.php path/to/your-script.js
若要进行 PHP 级性能分析,Xdebug 的分析器可像分析其他 PHP 应用一样,直接分析 Phasis。在 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 — 直接写入 opcache 格式的操作码,跳过 JsToPhp 的
eval()步骤。这是最深入的改动;其他改进都能在现有架构内完成。
对于 Phasis 面向的嵌入式工作负载,这些改进都不是必需的。