干了这么多年PHP,早就习惯了它的解释执行模式。写完代码保存,刷新浏览器就能看到结果,这种即时反馈的爽快感是编译型语言给不了的。但代价也很明显——在计算密集型场景下,PHP的运行效率确实差强人意。

今年六月,Swoole团队干了一件大事:他们把原来的Swoole AOT编译器项目正式更名为TypePHP,并且宣布国庆节全量开源。简单来说,这玩意儿能把PHP源码直接编译成C++17,再编译成原生机器码。不是字节码缓存,也不是JIT,是真正的AOT(Ahead-Of-Time)提前编译。

这到底意味着什么

以前我们聊PHP性能优化,翻来覆去就是OPcache、JIT、Swoole协程这些手段。OPcache省的是编译开销,JIT是运行时热点编译,Swoole解决的是IO并发。但它们都没有跳出Zend虚拟机的范畴,代码最终还是在VM里跑。

TypePHP不一样。它直接把PHP翻译成C++代码,然后用系统编译器生成可执行文件、PHP扩展或者共享库。这意味着编译后的代码直接跑在CPU上,没有VM的解释开销,没有zval的动态类型开销。对于大量数学计算、数据处理、加密解密这类场景,理论上的性能提升是数量级的。

我特意去翻了一下它的示例。一个简单的循环计算,原生PHP跑0.8秒,TypePHP编译后只需要0.05秒,差距确实很夸张。当然这是极端场景,实际业务中Web请求大部分时间在等数据库和IO,提升不会这么明显。但如果你有离线计算、队列消费、图像处理这类后台任务,TypePHP就很有价值了。

原生类型系统是关键

TypePHP性能提升的核心不只是AOT编译,还有它的原生类型系统。在文件顶部声明use native_types;之后,标量类型会直接映射成C++的原生类型。int对应int,float对应double,string对应std::string。这样一来,变量不再是zval,不需要引用计数,不需要类型推断,内存布局和访问方式都是C++级别的。

这其实是PHP一直以来的痛点。为了灵活性,PHP的变量是动态类型,一个zval结构体里存着类型和值,每次操作都要检查类型、处理引用计数。写起来很爽,但跑起来很慢。TypePHP相当于给了你一个开关:在需要性能的地方,牺牲一点灵活性,换取原生速度。

不过要注意,use native_types是文件级别的声明,不是整个项目都要开。你可以在热点计算的文件里启用,其他文件保持PHP的动态特性。这种渐进式的设计思路很务实,不需要为了性能重构整个项目。

编译器本身是PHP写的

让我觉得很有意思的一点是,TypePHP的编译器本身就是用PHP写的。这意味着PHP开发者可以直接参与贡献,不需要去学C++编译器开发。而且它的构建流程也不复杂,composer安装之后就能用。

目前支持三种编译模式:生成独立可执行文件、生成PHP扩展、生成共享库。独立可执行文件可以直接在服务器上跑,不需要装PHP环境;PHP扩展可以和现有PHP代码混用,把热点函数编译成扩展加载;共享库则适合嵌入到其他系统中。

我个人最看好的是PHP扩展模式。现有项目不用大改,把性能瓶颈的几个类编译成扩展,其余代码继续用PHP写,性价比最高。

还需要冷静看待

虽然TypePHP的前景很诱人,但现在还不是大规模落地的时候。测试版刚出来,国庆节才全量开源,很多语法特性还在完善中。比如PHP的动态特性、反射、eval这些东西,AOT编译处理起来很麻烦,目前的支持程度还不够。

而且编译型语言的开发体验和PHP完全不同。改一行代码要重新编译,调试也没有现在这么方便。对于快速迭代的Web业务来说,这个代价可能不值得。TypePHP的定位应该是补充,不是替代。

我打算等它开源稳定后,先在项目里的一个计算密集型队列任务上试试。如果效果好,再逐步推广到其他场景。但核心业务代码,短期内还是会老老实实写原生PHP。

PHP这几年的变化确实越来越有意思了。从JIT到FFI,从Swoole到TypePHP,这个"世界上最好的语言"一直在试图突破自己的边界。不管TypePHP最终能走多远,这种探索本身就值得尊重。