将 PHP 编译为原生二进制文件(无需解释器)
大约十五年前,Facebook 曾尝试用 HPHPc 编译器解决其单体应用的性能问题,该编译器将 PHP 转换为 C++。后来他们放弃了这一方案,转而采用 HHVM 虚拟机,而 PHP 8 引入了内置的 JIT 编译器。看起来纯 Ahead-Of-Time(AOT)编译器的概念似乎已经彻底成为历史。然而 Swoole 团队决定重新审视这一方向,并发布了 TypePHP 项目。
这是一个用 PHP 本身编写的完整 AOT 编译器。它将 PHP 8.4+ 源代码转换为 C++17,然后通过 GCC 或 Clang 编译成原生机器码。运行时无需操作码、无需 JIT 预热,也无需解释器。
该编译器是完全自托管的。phpc 工具从 PHP 源代码构建自身,编译器内部不包含任何 C 粘合代码。
TypePHP 的工作原理
编译分两个阶段进行。首先,分析器解析项目代码,收集类和函数的元数据,并构建完整的符号表。在第二阶段,函数体被翻译成 C++17。在热路径中,编译器生成快速的静态 C++ 代码,而对于反射或系统函数调用等动态构造,它通过 PHPX 层连接到 Zend 运行时。
输出可以是三种格式之一:
- 可执行文件(.bin)。用于控制台工具和守护进程的独立文件,需要一个入口点 main()。
- PHP 扩展(.so/.dll)。可加载到标准 PHP 中的常规 .so 或 .dll 模块。
- 动态库(.a/.so)。包含生成的 .h 文件的二进制文件,可供其他项目调用。
- WASI 组件,用于在 WebAssembly 环境中运行。
语法上的变化
TypePHP 并不试图支持标准 PHP 的所有动态特性。创作者们在需要最大性能的地方押注于严格类型化。
原生标量类型
declare(strict_types=1) 指令告诉编译器将 int、float 和 bool 直接映射到 C++ 类型(int64_t、double、bool)。使用这种类型的变量在函数执行过程中不能再突然改变类型。但处理器运行的是纯机器指令,无需解包 zval。
构建命令:
严格类型化的容器
常规 PHP 数组是通用的,但因哈希表而内存消耗大且速度慢。TypePHP 添加了 Vec、Map、Set 和 HashMap 结构。
在开发者测试中,使用 Vec 的循环更新元素耗时 6.4 秒,而启用 JIT 的常规 PHP 数组耗时 67.6 秒。速度实际上与手写的 C++ vector(6.2 秒)相当。
基元类型上的方法
不再使用一堆函数如 strlen()、substr() 或 strpos(),你可以直接在基元类型上调用方法。编译器在构建时解析这类调用,并将其转换为直接的 C 函数调用,无需虚表开销:
通过属性生成模板代码
为了避免手动编写大量 getter 和 setter,TypePHP 在编译期间处理自定义属性:
#[With] 属性生成一个克隆对象、更改字段并返回新实例的方法。这对于不可变的 DTO 很方便。
直接 C++ 集成
如果某个算法在 PHP 中缺失,你可以用 C++ 编写并放在旁边。通过带有空体的 stub 文件进行链接:
基准测试中的性能
根据作者在 phphp-bench 仓库的标准综合测试中的测量结果(启用 -O3 标志的 Mandelbrot 和 Fannkuch):
- Mandelbrot 在 0.603 秒内完成,而标准解释器需要 5.034 秒(约 8 倍加速);
- Fannkuch 在 2.021 秒内完成,而标准解释器需要 13.045 秒(6.5 倍加速)。
这些数字对于数学和分支的 AOT 编译来说是预期的。在实际 Web 应用中,大部分时间花在 I/O 和数据库操作上,所以改进会更有限,但对于计算任务和后台工作来说,差异是明显的。
局限性和权衡
目前还无法将现有的 Laravel 或 Symfony 项目移植到 TypePHP。有一些严格的规则:
- 全局作用域中禁止可执行代码——所有代码必须位于函数或方法内。
- 需要 PHP 8.4 或 8.5 以及编译后的 libphp 库(embed SAPI)来构建二进制文件。
- 需要 GCC 9+ 支持 C++17、CMake 以及用于精确计算的 GMP/MPFR 库。
- 一些动态语言特性(如自由类型切换或复杂引用)故意不被支持。
typephp.json 配置文件有助于管理大型项目中的依赖项和优化标志:
谁将从这个项目中受益
TypePHP 正在积极开发中。目前在 GitHub 上的星标数不到一千,但该项目由经验丰富的 Swoole 团队支持。
如果需要以下场景,尝试这个项目是有意义的:
- 构建轻量级 CLI 工具或微服务作为单个二进制文件,无需在目标服务器上安装 PHP。
- 在向客户本地交付时隐藏应用程序源代码,因为二进制文件比字节码更难反编译。
- 编写包含大量计算的 PHP 扩展,而无需深入了解 C 和 Zend API。
- 加速隔离的计算模块,如解析器、数据打包器或评分算法。
要开始使用,只需克隆仓库,通过 php build.php 构建测试脚本,然后查看 build 目录中生成的 C++ 代码。
相关项目