PHP 8.6 闭包优化
PHP 8.6 Closure Optimizations

原始链接: https://wiki.php.net/rfc/closure-optimizations

此 RFC 提出针对 PHP 闭包(包括箭头函数)的两种性能优化,并承认可能存在向后兼容性(BC)破坏。 首先,**静态闭包推断** 旨在自动识别不使用 `$this` 的闭包并将其转换为静态闭包。 这将破坏引用循环——一种常见的性能瓶颈——闭包和它所定义的对象会不必要地保持彼此存活。 在 Symfony 上的测试表明,静态闭包推断的成功率约为 78%。 其次,**无状态闭包缓存** 重用不捕获任何变量的相同静态闭包,从而显著减少对象实例化。 基准测试显示,在合成测试中性能提升高达 80%,在 Laravel 模板中提升约 3%。 潜在的 BC 破坏包括:`ReflectionFunction::getClosureThis()` 对于推断出的静态闭包返回 `NULL`,来自同一位置的无状态闭包相同,以及更早的对象收集/销毁。 然而,这些更改旨在最大限度地减少破坏,特别是对于 `Closure::bind()`,它将继续按预期为显式静态闭包工作。 需要 2/3 多数票才能通过,投票时间为 2026 年 2 月 27 日至 3 月 13 日。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 PHP 8.6 闭包优化 (php.net) 8 分,作者 moebrowne 2 小时前 | 隐藏 | 过去 | 收藏 | 2 条评论 帮助 hyperionultra 2 分钟前 | 下一个 [–] ~3% 性能提升。我不理解关于 `$this` 的部分。 moebrowne 2 小时前 | 上一个 [–] > 非静态闭包会在保证不使用 `$this` 的情况下转换为静态闭包。 > 无状态闭包,即静态闭包,不会捕获任何变量,也不会声明任何静态变量,它们会在使用之间被缓存。 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Introduction

This RFC proposes two new optimizations for closures (including arrow functions) that come with some theoretical BC breaks. The purpose of this RFC is to evaluate whether these BC breaks are an acceptable trade-off for the gained performance.

  • Non-static closures are turned into static ones if they are guaranteed not to make use of $this.

  • Stateless closures, i.e. those that are static, don't capture any variables and don't declare any static variables, are cached between uses.

Static closure inference

This optimization will attempt to infer static for closures that are guaranteed not to make any use of $this.

class Foo {
    public $closure;
 
    public function __construct() {
        $this->closure = function() {
            echo "Hello world!";
        };
 
        // Or
 
        $this->closure = fn($a, $b) => $a + $b;
    }
}

Previously, the closure in __construct would have implicitly captured $this, keeping the instance of Foo alive for the lifetime of the closure. Conversely, the instance of Foo would keep the closure alive, creating a reference cycle that requires running PHP's cycle collector to resolve. Frequently, such cycles are not resolved for the remainder of the request, given the cycle collector often doesn't run at all. These cycles can also make it more likely for the cycle collector to run in the first place, spending time on resolving a cycle that didn't need to exist in the first place.

The aforementioned optimization will attempt to infer static for closures that fulfill the following (slightly esoteric) conditions. The closure must not:

  1. use $this. That's the obvious case.

  2. use $$var, given $var could refer to 'this'.

  3. use Foo::bar(), given this could be a hidden instance call to a (grand-)parent method.

  4. use $f(), for the same reason as 3.

  5. use call_user_func(), for the same reason as 3.

  6. declare another (uninferable) non-static closure, where $this flows from parent to child.

  7. use require, include or eval, given the called code might do any of the above.

These rules appear to be quite effective. A test was performed on Symfony Demo, where static modifiers were removed from all closures. The optimization was able to infer 68/87 (~78%) closures that were explicitly marked as static.

While explicit marking remains preferable, this optimization aims to benefit codebases that prefer not to add static to avoid visual clutter, as well as those who aren't aware of these subtle performance implications.

Stateless closure caching

Stateless closures, i.e. those that are static, don't capture any variables and don't declare any static variables, are cached between uses.

function test() {
    $x = static function () {};
}
for ($i = 0; $i < 10_000_000; $i++) {
    test();
}

Previously, this would have created 10 000 000 closure instances, even though all closures are effectively identical. With this second optimization, the first closure will be kept alive and cached for reuse. This small (and very synthetic) benchmark improves by ~80% on my machine. More practical improvements were also measured in the Laravel template, where these two optimizations can avoid 2384 out of 3637 closure instantiations, improving performance by ~3% on my machine.

Backward Incompatible Changes

There are three BC considerations.

  • ReflectionFunction::getClosureThis() will now return NULL for closures that are inferred as static. This might be slightly surprising, given static inference does not work 100% reliably, limited by the rules previously described.

  • Two stateless closures originating from the same lexical location will now be identical. I.e.:

    function test() {
        return function () {};
    }
    test() === test(); // true
 
  • Objects that previously would have created cycles may be collected earlier, also triggering destructors earlier. While technically backward incompatible, this behavior is generally expected and more predictable.

Of note is that Closure::bind() and Closure::bindTo() usually throw when attempting to bind an object to a static closure. In this RFC, passing an object to these methods is explicitly allowed and discarded only for closures that are inferred as static, but not those that are explicitly static. The aim is to retain backward compatibility when a closure can suddenly be inferred as static due to seemingly unrelated changes, such as removing a static method call.

Vote

Primary Vote requiring a 2/3 majority to accept the RFC:

Voting opened on 2026-02-27 and closes on 2026-03-13.

References

联系我们 contact @ memedata.com