编程 PHP 8.6 闭包优化只合了一半:无状态闭包缓存已落地,static 推断被撤回

2026-09-30 21:02:29

PHP 8.6 闭包优化只合了一半:无状态闭包缓存已落地,static 推断被撤回

PHP 8.6 的 Closure optimizations RFC(作者 Ilija Tovilo,2026-01-30,状态 Partially implemented)原本提出两个闭包(含箭头函数)优化,附带理论上的 BC break。投票于 2026-02-27 开启、2026-03-13 关闭,要求 2/3 多数。最终只合并了其中一个:无状态闭包缓存。另一个“非 static 闭包自动推断为 static”因边缘情况被撤回。

未落地的 static 推断

原优化一是:无法推断使用 $this 的非 static 闭包,自动变成 static。判断条件偏 esoteric,闭包需要不满足以下任一条件:

  • 不使用 $this;
  • 不使用 $$var($var 可能指向 $this);
  • 不使用 Foo::bar()(可能是对父/祖父方法的隐式实例调用);
  • 不使用 $f()(同因);
  • 不使用 call_user_func()(同因);
  • 不在内部声明另一个(无法推断的)非 static 闭包($this 从父流向子);
  • 不使用 require/include/eval(被调代码可能做上述任何事)。

2026-08-11 errata 说明了撤回原因:投票结束后发现边缘情况。内部函数 array_map() 可以仅凭一个命名 callable 通过前一个栈帧做实例调用;许多其他内部调用以及 array callable(['Foo','instanceCall'])同理。这种 esoteric 行为应先被弃用再移除,不能直接假定没人用。因此只合并了 RFC 中的“无状态闭包缓存”部分。所有相关函数都被正确标注 static 的代码才能拿到全部性能收益。

验证 static 推断未生效:

$closure = function () {
    return 1;
};

$ref = new ReflectionFunction($closure);
var_dump($ref->isStatic()); // false

这个闭包不使用 $this,但 PHP 8.6 不会把它推断成 static。

已落地的无状态闭包缓存

原优化二是:无状态闭包(static、不捕获变量、不声明静态变量)在使用之间缓存复用。此前即使循环里生成一百万个逻辑相同的闭包,也会创建一百万个实例;优化后第一个闭包被保留并复用。

合成基准提升约 80%;更实际的 Laravel 模板测试中,3637 次闭包实例化里有 2384 次可被省掉,性能提升约 3%。

同一词法位置、static、不捕获变量的闭包现在复用同一实例,可以用 === 判等:

function make(): Closure
{
    return static fn () => 42;
}

$a = make();
$b = make();
var_dump($a === $b); // true

如果闭包捕获变量,则不会进入缓存复用:

function makeWithCapture(int $x): Closure
{
    return static fn () => $x;
}

$c = makeWithCapture(1);
$d = makeWithCapture(1);
var_dump($c === $d); // false

第一类可调用缓存

另据 PHP 8.6 NEWS:Core 新增 first-class callable cache,在请求周期内共享实例。

function hello(): void {}

$a = hello(...);
$b = hello(...);
var_dump($a === $b); // true

BC 影响

提案中列出的 Backward Incompatible Changes 包括:

  • ReflectionFunction::getClosureThis() 对被推断为 static 的闭包将返回 NULL(因 static 推断并非 100% 可靠,可能让人意外)。
  • 来自同一词法位置的两个无状态闭包现在会判定为相同(identical)。
  • Closure::bind()/bindTo() 通常对 static 闭包抛异常;本 RFC 中,对“被推断为 static”(而非显式 static)的闭包,传入对象会被显式允许并丢弃,以保留向后兼容。

由于最终只合并了“无状态闭包缓存”,与 static 推断相关的 BC 未随本次合并生效;实际落地的主要是同一词法位置的无状态闭包会被判定为相同。

要在 PHP 8.6 上拿到全部性能收益,需要显式给闭包加 static。

复制全文 生成海报 PHP 性能 语言特性

推荐文章

程序员茄子在线接单