Skip to content

Share the method's runtime cache with fake closures of the same scope - #23291

Open
staabm wants to merge 1 commit into
php:masterfrom
staabm:closure
Open

Share the method's runtime cache with fake closures of the same scope#23291
staabm wants to merge 1 commit into
php:masterfrom
staabm:closure

Conversation

@staabm

@staabm staabm commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

disclaimer: this change was generated by claude opus. I have little experience with php-src development


Creating a fake closure (Closure::fromCallable(), first-class callable syntax, ReflectionMethod::getClosure()) over a method whose runtime cache was not yet initialized allocated a cold per-closure heap cache on every creation. Initialize the method's own shared cache instead - exactly what the first real call would do - so repeated closure creations reuse one warm cache. Trampolines and heap-cache functions keep the per-closure path.


after this PR

➜  php-src git:(closure) ✗ hyperfine 'sapi/cli/php -n -d opcache.enable_cli=0 closure_from_callable_runtime_cache_bench.php'
Benchmark 1: sapi/cli/php -n -d opcache.enable_cli=0 closure_from_callable_runtime_cache_bench.php
  Time (mean ± σ):     136.5 ms ±   1.4 ms    [User: 132.8 ms, System: 2.3 ms]
  Range (min … max):   133.4 ms … 138.7 ms    21 runs

before this PR

➜  php-src git:(closure) ✗ hyperfine 'sapi/cli/php_old -n -d opcache.enable_cli=0 closure_from_callable_runtime_cache_bench.php'
Benchmark 1: sapi/cli/php_old -n -d opcache.enable_cli=0 closure_from_callable_runtime_cache_bench.php
  Time (mean ± σ):     150.6 ms ±   1.3 ms    [User: 147.2 ms, System: 2.3 ms]
  Range (min … max):   148.3 ms … 152.9 ms    19 runs

benchmark closure_from_callable_runtime_cache_bench.php: https://gist.github.com/staabm/20c082674ed880c8fc097b4775f2d7a7

@staabm
staabm requested a review from dstogov as a code owner August 15, 2026 09:27
staabm referenced this pull request in ondrejmirtes/php-src Aug 15, 2026
Creating a fake closure (Closure::fromCallable(), first-class callable
syntax, ReflectionMethod::getClosure()) over a method whose runtime
cache was not yet initialized allocated a cold per-closure heap cache
on every creation. Initialize the method's own shared cache instead -
exactly what the first real call would do - so repeated closure
creations reuse one warm cache. Trampolines and heap-cache functions
keep the per-closure path.
Creating a fake closure (Closure::fromCallable(), first-class callable
syntax, ReflectionMethod::getClosure()) over a method whose runtime
cache was not yet initialized allocated a cold per-closure heap cache
on every creation. Initialize the method's own shared cache instead -
exactly what the first real call would do - so repeated closure
creations reuse one warm cache. Trampolines and heap-cache functions
keep the per-closure path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants