
来源:X(Twitter) 技术推文,完整整理版
本次基准测试采用简单整数循环:总计一亿次迭代,每一轮计算都依赖上一轮输出结果。通过掩码将变量 $h 限制在30位,理论上不会发生数值溢出。即便如此,PHP 的溢出语义,却极大影响了最终的测试数据。
在 PHP 中,整数溢出会自动转为浮点数。这就导致 $h * 31 不能直接简化为一条机器乘法指令;运行时必须处理计算结果不再是整数的可能性。不同技术实现,都要为这条语言规则付出各自的性能代价。
针对整数运算使用专用处理逻辑,中间计算结果存放在预分配的虚拟机槽位,不需要创建临时对象。 测试结果:652 毫秒,单次迭代执行 112 条机器指令。
JIT 消除了操作码分发开销,但无法绕开类型检查。调试信息标记乘法结果为 [!long],JIT 无法保证运算后仍然是整数,乘法、加法依旧要调用 Zend 底层处理函数。 即便如此,去掉虚拟机分发依然带来性能收益:485 毫秒,单次迭代 85 条机器指令。
将 PHP 代码转译为 C++,PHP 变量封装为携带 zval 的 php::Variant 对象。 虽然不再有操作码分发,但会大量产生临时对象:乘法生成一个对象、加法生成一个、按位与运算又生成一个。单次迭代一共触发13次函数调用,其中包含6次析构调用。
运算走通用 Variant 接口,而非专门的整数处理逻辑。又因为调用跨越共享库边界,C++ 编译器无法做内联优化,也不能对整个循环做整体优化。 测试结果:3996 毫秒,单次迭代 491 条机器指令,性能远低于原生PHP解释器。
不依赖 Zend VM,直接生成机器码。但运算中间值需要在堆上做装箱处理,以此同时支持整数、浮点数两种类型。 性能采样显示:63% 的耗时消耗在内存分配与引用计数,21% 消耗在装箱、拆箱操作。 测试结果:5202 毫秒,单次迭代 925 条机器指令。
堆装箱并不是 PHP 语义强制要求,属于 Elephc 当前版本实现层面的问题,项目已经标记为待优化项。
当 TypePHP 开启 native_types,情况彻底改变。 变量 $h 直接映射为原生 int64_t,不再遵循 PHP“溢出转浮点数”的规则,直接使用CPU硬件整数溢出行为。C++编译器可以对整个循环做深度优化。
测试结果:单次迭代仅8条机器指令,耗时90毫秒,和同等逻辑C程序(89毫秒)几乎持平。
语义差异对比:
● PHP:输出约 1.8e19,溢出自动转为浮点数
● TypePHP(native_types):输出 0,硬件直接整数溢出
重点:性能暴涨并不是编译本身带来的,而是程序切换到另一套算术规则换来的。
PHP 数组是通用哈希表,支持不同类型的值,键可以是数字也可以是字符串,本身开销很高。 TypePHP 可以将它替换为强类型的 std::vector。
基准测试中,仅这一处改动,执行时间从 417ms 下降至 14ms,性能提升接近30倍。 底层逻辑同理:它已经不再是PHP原生数组,而是换成约束更强的强类型容器。
这个基准告诉我们:单纯提问“编译版PHP是否比解释PHP更快”,并没有抓住核心问题。
● 如果完整保留 PHP 全部语义:编译会带来小幅提升,但上限有限。JIT 从652ms降到485ms,主要收益来自移除虚拟机分发。
● 真正的巨大性能飞跃,来源于替换:PHP算术 → 原生整数运算、PHP哈希数组 → 强类型向量容器。
所以更值得思考的问题,不是PHP能被编译到多快;而是:需要舍弃多少 PHP 语言特性,程序才能达到 C 级别的运行速度。