📌 项目地址:swoole/typephp | ⭐ 766 颗星 | 🔧 PHP | 📜 未标注
PHP 性能优化过去二十年只有两条路:OPcache 缓存字节码,PHP 8 的 JIT 在运行时把热点 opcode 编成机器码。两条路都困在 VM 执行模型里。Swoole 团队的 TypePHP 走了第三条:构建期直接把 PHP 源码降到 C++17,再编译成原生机器码。产物有原生可执行文件、PHP 扩展、共享库三种形态,架构图里还出现了 WASI component。
没有解释器,没有 opcode cache,没有 JIT 预热。冷启动就是满速。
编译器编译了自己
这是我觉得整个项目最硬的地方。
TypePHP 编译器本身完全用 PHP 写成,bootstrap 链路里没有任何 C/C++ 胶水代码。它的 tpc 编译器二进制,是用 TypePHP 编译它自己的 PHP 源码得到的。
为什么这件事有分量?编译器要支持的每一种语言特性,都先在自己身上跑通过一遍。自举是编译器工程里公认的验证标准,GCC、LLVM、Rust 都走过这条路。一个 766 Star 的新项目直接以自举状态发布,说明作者对“支持子集”的边界是认真划过的——没有认真划过边界,自举根本跑不通。
不是把整个 PHP 静态化
细看架构,TypePHP 是混合模型。
编译后的用户函数不再以 Zend opcode 执行,直接跑原生代码。但动态 PHP 值、内部函数、反射、对象元数据,仍然通过 PHPX 与 Zend 运行时互操作。它做的是“热路径静态化 + 动态部分走 Zend”,没有把 PHP 强行改造成纯静态语言。
代价是要补编译期类型信息。源码需要配合 .stub.php 声明文件,构建时还可以混入可选的 C/C++ 源码。
两阶段编译,为了确定性
编译流程里有个容易被忽略的设计。
prepare 阶段构建完整符号模型,但不分配运行时缓存 ID;常量和声明默认值在这个阶段保留 AST。等 convert 阶段——所有项目符号都已知之后——才统一降级到 C++17。
为什么要分两步?单文件编译时你不知道别的文件定义了什么。两阶段设计保证多文件构建和自举构建的确定性。配合可复用的 object/PCH 缓存,增量编译也能提速。
四平台 CI
CI 覆盖 Linux x64、Linux ARM64、macOS ARM64、Windows,各一个独立 workflow。对新编译器项目来说,这个覆盖范围算扎实。
上手前必须知道的事
README 没有给出安装命令、tpc 调用参数或 .stub.php 的具体写法,实际用法要去仓库 docs/ 目录找。
项目明确声明:有意只支持一个定义清晰、可测试的 PHP 子集,不承诺对任意动态 PHP 程序开箱即用。迁移现有项目前,先读 docs/en/INCOMPATIBLE_PHP_FEATURES.md。这不是走过场的免责声明——高度动态的代码(大量 eval、运行时魔术方法)大概率不在支持范围内。项目还在活跃开发期,API 和行为可能变动,生产采用前自己掂量。
同类方案对比
“PHP 编译成原生代码”这条路的前辈不少。Phalanger 编译到 .NET,早已停滞;Hack 干脆另起一套语法,脱离了 PHP 生态。TypePHP 的差异点:保留 PHP 语法、Swoole 官方维护、自举。前两条决定存量 PHP 开发者的迁移成本,第三条决定工程质量下限。
适合谁盯:想把 CLI 工具做成单二进制分发的 PHP 开发者,用 Swoole 跑常驻服务、在意启动延迟的团队,以及对编译器自举过程好奇的人。最后一类光是把仓库 clone 下来读 bootstrap 流程,就值回时间。