Zend 引擎的工作原理
追踪 PHP 从源代码到操作码再到执行的过程
Zend 引擎的工作原理 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。
引擎内部
PHP 不会从源代码开始逐行解释执行。Zend 引擎会将您的脚本编译为称为操作码的中间表示,然后由虚拟机执行这些操作码。理解这一流程有助于您理解性能、OPcache 和即时编译。
本课将依次讲解源代码 → 令牌 → AST → 操作码 → VM 执行。
流程
对 PHP 文件的每次请求都会经历四个阶段:
- 词法分析 — 源代码文本 → 令牌(基于 re2c 的扫描器)
- 语法分析 — 令牌 → 抽象语法树(Bison 语法)
- 编译 — AST → 操作码数组(op_array)
- 执行 — Zend VM 遍历操作码
没有 OPcache 时,前三个阶段会在每次请求中重复执行。
生成令牌
词法分析器会将字符转换为 T_VARIABLE、T_ECHO、T_STRING 等令牌。PHP 通过 token_get_all() / PhpToken 提供此阶段的功能,这正是 PHP-CS-Fixer 等工具使用的机制。
<?php
$src = '<?php $x = 1 + 2; echo $x;';
foreach (PhpToken::tokenize($src) as $tok) {
if ($tok->isIgnorable()) continue; // skip whitespace
printf("%-12s %s\n", $tok->getTokenName(), trim($tok->text));
}
?>AST
令牌经过解析后会形成一棵由节点组成的树——其中一个赋值节点的子节点是一个变量和一个二元运算表达式。PHP 会在内部构建此 AST,而 nikic/php-parser 则会在用户态重建等价的 AST,供 PHPStan 等静态分析工具使用。
<?php
// Conceptual AST for: $x = 1 + 2;
//
// AST_ASSIGN
// ├── AST_VAR ($x)
// └── AST_BINARY_OP (+)
// ├── 1
// └── 2
//
// At compile time PHP folds 1 + 2 into a literal 3
// (constant folding) before generating opcodes.
echo "AST drives opcode generation\n";
?>操作码
编译器会生成一个 op_array:一个由操作码组成的扁平列表。每个操作码都有一个操作码编号(例如 ZEND_ADD、ZEND_ECHO、ZEND_ASSIGN),以及最多两个操作数和一个结果;它们分别可以是已编译变量(CV)、临时变量(TMP)或常量。
<?php
// Opcodes for: $x = 1 + 2; echo $x;
//
// line op operands result
// --- ------------ ----------------- -------
// 1 ADD 1, 2 ~0
// 1 ASSIGN $x, ~0
// 1 ECHO $x
// 1 RETURN 1
//
// ~0 is a temporary; $x is a compiled variable (CV).
echo "op_array is what OPcache stores\n";
?>检查操作码
您可以使用 VLD 扩展或 OPcache 的 opcache.opt_debug_level 转储生成的操作码。这可以显示常量折叠、死代码消除,以及控制流如何转换为 JMP/JMPZ 操作码。
# Dump opcodes with VLD
php -d vld.active=1 -d vld.execute=0 script.php
# Or via OPcache optimizer debug (pre/post optimization)
php -d opcache.opt_debug_level=0x10000 script.php # before opt
php -d opcache.opt_debug_level=0x20000 script.php # after opt已编译变量(CV)
在已编译的代码中,局部变量并不是通过哈希表查找的——编译器会为每个局部变量分配一个编号CV 槽位。第二次访问 $x 时执行的是数组索引访问,而不是符号表搜索。这是局部变量速度很快的主要原因之一。
<?php
// Each named local gets a fixed CV slot at compile time:
// $a -> CV0 $b -> CV1 $sum -> CV2
function add(int $a, int $b): int {
$sum = $a + $b; // ADD CV0, CV1 -> CV2
return $sum; // RETURN CV2
}
echo add(2, 3) . PHP_EOL;
?>VM 执行循环
执行器(execute_ex)会遍历 op_array。每个操作码都对应一个处理函数;PHP 可以将这种分派实现为巨型分支语句、计算跳转或混合方式(默认方式,在支持该方式的编译器上速度最快)。opline 指针会不断前进;用于控制流的 JMP 操作码会移动该指针。
<?php
// if ($n > 0) echo 'pos'; compiles roughly to:
//
// IS_SMALLER 0, $n -> ~T
// JMPZ ~T, ->L1 ; if false, skip
// ECHO 'pos'
// L1:
// RETURN 1
//
// The VM follows opline; JMPZ rewrites it conditionally.
$n = 5;
if ($n > 0) echo "pos\n";
?>OPcache 的作用
OPcache 会将已编译的共享内存中的 op_array缓存起来,从而在后续请求中跳过词法分析、语法分析和编译。它还会运行一次优化器处理(常量折叠、死代码删除和操作码融合)。每次请求中,VM 仍会执行缓存的操作码——这正是即时编译之后能够发挥作用的地方。
函数调用开销
函数调用会压入一个新的 VM 栈帧:操作码 INIT_FCALL,每个参数对应一个 SEND_VAL/SEND_VAR,然后执行 DO_FCALL。了解这一点可以解释为什么过多的小型函数调用会产生可测量的开销,也能说明为什么内联和即时编译在热点循环中很重要。
<?php
// square($x) compiles to a call sequence:
// INIT_FCALL 'square'
// SEND_VAR $x
// DO_FCALL -> ~R
// ASSIGN $y, ~R
function square(int $x): int { return $x * $x; }
$total = 0;
for ($i = 1; $i <= 5; $i++) {
$total += square($i); // one call sequence per iteration
}
echo $total . PHP_EOL; // 1+4+9+16+25 = 55
?>请求结束
执行完成后,PHP 会拆除请求环境:释放运行中的变量、刷新输出,然后引擎重置其分配器区域。在共享无状态的 FPM 模型中,每个请求都会从干净的状态开始——因此一个请求中的致命错误不会破坏另一个请求。OPcache 的共享 op_arrays 会跨请求保留,但执行器状态不会保留。
快速检查
OPcache 究竟存储了什么,因而可以跳过重新编译?
回顾
Zend 流程依次为词法分析 → 语法分析 → 编译 → 执行。源代码会变成令牌,然后变成 AST,之后变成由 VM 执行器循环运行的op_array操作码列表;局部变量存储在编号的 CV 槽位中,而函数调用会压入栈帧。OPcache 会缓存 op_array(并执行一次优化器处理),因此每个请求只需重复执行。
常见问题解答
「Zend 引擎的工作原理」课时是免费的吗?
是的 — 「Zend 引擎的工作原理」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。
「Zend 引擎的工作原理」这节课中我会学到什么?
追踪 PHP 从源代码到操作码再到执行的过程 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 PHP Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「Zend 引擎的工作原理」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 PHP Academy 课中编写并运行代码吗?
能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- Zend 引擎的工作原理
- 内存管理与垃圾回收
- OPcache 与 JIT 编译
- 使用 C 编写基础 PHP 扩展