PHP 代码必须被翻译成机器码,由 CPU 逐条执行的庖丁解牛

张开发
2026/4/16 9:03:21 15 分钟阅读

分享文章

PHP 代码必须被翻译成机器码,由 CPU 逐条执行的庖丁解牛
更准确的说法是PHP 代码首先被翻译成中间代码 (Opcode)由 Zend 虚拟机 (VM) 解释执行而在特定条件下如开启 JIT热点 Opcode 会被进一步编译成原生机器码直接由 CPU 执行。这是一个两级翻译的过程。如果把这个过程比作跨国交流PHP 源码中文小说。Opcode世界语译文通用、标准化但人读起来还是慢。Zend VM翻译官拿着世界语译文逐句念给听众听解释执行。JIT Machine Code针对高频金句直接翻译成听众的母语英语/机器码听众直接理解无需翻译官介入。CPU听众的大脑最终处理信息的地方。一、第一级翻译从源码到 Opcode (Compilation)PHP 是一种脚本语言它不直接生成.exe或二进制文件。它的第一步是编译成Opcode (Operate Code)。1. 词法分析 (Lexing)输入$a 1 2;工具re2c(Lexer)。输出Token 流。T_VARIABLE ($a)T_ASSIGN ()T_LNUMBER (1)T_PLUS ()T_LNUMBER (2);2. 语法分析 (Parsing)工具bison(Parser)。动作根据 PHP 语法规则将 Token 流构建成抽象语法树 (AST)。AST_ASSIGN ├── var: AST_VAR (name: a) └── expr: AST_BINARY_OP (PLUS) ├── left: AST_ZVAL (1) └── right: AST_ZVAL (2)3. 编译成 Opcode工具Zend Compiler。动作遍历 AST生成线性执行的指令序列。输出 (Opcode)ZEND_ADD ~0, 1, 2 ; 1 2结果存入临时变量 ~0 ZEND_ASSIGN !0, ~0 ; 将 ~0 赋值给编译变量 !0 ($a) ZEND_RETURN 1 ; 返回关键点此时还没有机器码。Opcode 是 Zend VM 能理解的“汇编语言”但它依然是数据不是 CPU 指令。 核心洞察OPcache 的作用就是缓存这些 Opcode。下次请求时跳过 Lexing/Parsing/Compiling直接加载 Opcode。这节省了 CPU 周期但执行依然靠 Zend VM 解释。二、第二级执行Zend VM 的解释循环 (Interpretation)这是 PHP 8 之前以及未开启 JIT 时的主要执行方式。CPU 并没有直接执行 PHP 逻辑而是在执行 Zend VM 的代码。1. VM 的核心结构Zend VM 本质上是一个巨大的switch-case循环或基于计算跳转的线程化调度器。// 伪代码Zend VM 执行引擎while(opcode!END){switch(opcode-handler){caseZEND_ADD:// C 语言实现的加法逻辑resultop1op2;break;caseZEND_ASSIGN:// C 语言实现的赋值逻辑assign_variable(var,value);break;// ... 几百个 case}opcode;}2. CPU 真正在执行什么当 PHP 执行$a 1 2;时CPU 实际上在执行取指读取ZEND_ADD这个枚举值。跳转跳转到case ZEND_ADD:的代码块地址。执行 C 代码执行编译器生成的、用于处理加法的原生机器码这是 C 语言写的 Zend 引擎部分。更新指针指向下一条 Opcode。循环判断回到while开头。开销每一次 PHP 语句的执行都伴随着多次 CPU 分支预测和函数调用开销。这就是为什么 PHP 比 C/C 慢的原因中间商 (Zend VM) 赚了大量差价 (CPU 周期)。三、第三级优化JIT 编译成原生机器码 (Native Compilation)PHP 8 引入的JIT (Just-In-Time)改变了游戏规则。它引入了第二层编译。1. 触发条件当某段 Opcode 被执行多次热点代码或者符合特定模式如纯数学运算、无复杂变量类型变化。2. 动态编译工具DynASM (Dynamic Assembler)。动作JIT 编译器读取热点 Opcode。将其直接翻译成x86_64 或 ARM64 机器码。将这段机器码写入一块可执行内存区域。3. 直接执行下次再执行这段逻辑时CPU绕过 Zend VM 的 switch-case 循环。CPU 的指令指针 (RIP/EIP) 直接跳转到 JIT 生成的机器码地址。执行CPU 直接执行加法、移动数据的原生指令。4. 例子对比非 JITCPU - Fetch Opcode - Switch Case - Execute C Function - ReturnJITCPU - Execute Native Instructions Directly 核心洞察只有开启了 JIT且代码是 CPU 密集型时PHP 代码才真正意义上“被翻译成机器码由 CPU 直接执行”。对于普通的 Web IO 业务JIT 收益有限因为瓶颈在 IO不在 CPU。四、硬件执行真相硅片上的舞蹈无论是否有 JIT最终都要落到硬件上。1. 指令流水线 (Pipeline)CPU 不是逐条串行执行而是并行处理取指、解码、执行、访存、写回。PHP 的影响Zend VM 的大量分支跳转 (switch-case) 容易导致流水线停顿 (Pipeline Stall)因为 CPU 猜不准下一个 Opcode 是什么。JIT 的优势生成的机器码是线性的、可预测的利于 CPU 预取和优化。2. 缓存友好性 (Cache Locality)Opcode 执行代码分散在 Zend 引擎的各个 C 函数中指令缓存 (I-Cache) 命中率低。JIT 执行热点代码紧凑地放在一起I-Cache 命中率高执行更快。3. 寄存器分配Zend VM大量使用内存 (zval结构体) 存储变量频繁读写 RAM。JIT智能地将热点变量放入CPU 寄存器(Registers)速度提升几个数量级。 总结原子化“执行链路”全景图阶段代码形态执行者CPU 行为性能特征1. 编译Source - AST - OpcodeZend Compiler计算哈希内存拷贝一次性开销 (OPcache 消除)2. 解释OpcodeZend VM (C Code)分支跳转函数调用慢开销大灵活3. JITOpcode - Machine CodeDynASM JIT生成原生指令编译开销但执行极快4. 执行Machine CodeCPU Core原生指令流水线最快直接硬件交互终极心法PHP 执行的本质是“抽象的层层剥离”。从高级语言到 Opcode再到机器码每一步都在用灵活性换取性能。Zend VM 是通用的桥梁JIT 是专用的捷径。理解这一链条你就知道了为什么 OPcache 必开而 JIT 需慎用。于源码中见逻辑于硅片中见指令以编译为眼解执行之牛于算力流转中求效率之真。行动指令查看 Opcode使用vld扩展 (php -dvld.active1 script.php) 查看你的代码生成的 Opcode。基准测试编写一个纯计算循环如斐波那契数列分别测试开启/关闭 JIT 的性能差异。观察热点使用opcache.jit_debug2查看 JIT 编译了哪些函数。思维升级记住对于 Web 应用减少 IO 等待比优化 CPU 执行更重要对于计算应用JIT 是 PHP 的救命稻草。

更多文章