Conversation
|
@dktapps Ping. I haven't done any benchmarking yet. |
|
This isn't caching static closures yet, right? So there should be no observable effect on performance |
Right. IIRC static closures themselves have a small benefit, though I didn't see it in the CI benchmark. I'm not sure if maybe Symfony already uses linting to add static. |
|
Okay, testing Symfony Demo with all static closures turned into non-static ones shows virtually no improvement. Regardless, they should benefit if we can cache them. I'll try this in the same PR then, given this one isn't useful on its own. |
|
One drawback of non-static closures is that they retain Big +1 on this. |
6159936 to
637bcd9
Compare
|
Quick test: In Symfony Demo with all static closures turned into non-static ones, this patch can turn 122/283 into static ones. That's ~43%, so not bad at all. How many of those can also be cached remains to be seen. |
|
Some other weird cases that might need to be considered:
|
|
To be honest I start to wonder if this actually makes sense considering the number of obscure conditions that might cause an unintended Has there been any discussion about a shorter syntax for static closures? e.g. something like |
|
Var var is already handled in this patch. I forgot about eval, but that's quite rare as well, especially in closures. |
727086f to
d67c388
Compare
a01304f to
b4bd2b2
Compare
|
Is there any movement on caching static stateless closures generally? I know the 1cc cache was merged, but there's plenty of cases where explicitly static closures could be cached too without the need for this inference. This inference would just be an added benefit to auto-static stuff in certain cases. |
|
Ofc, explicitly static closures will be cached too, assuming they are stateless. |
|
Ok, I hadn't realised caching was also in this PR. Good stuff |
AWS x86_64 (c7i.24xl)
Laravel 12.11.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)
Symfony 2.8.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)
Wordpress 6.9 main page - 100 consecutive runs, 20 warmups, 20 requests (sec)
bench.php - 100 consecutive runs, 10 warmups, 2 requests (sec)
|
ext/reflection/tests/ReflectionFunction__toString_bound_variables.phpt
Outdated
Show resolved
Hide resolved
165bbb9 to
43dc616
Compare
AWS x86_64 (c7i.24xl)
Laravel 12.11.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)
Symfony 2.8.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)
Wordpress 6.9 main page - 100 consecutive runs, 20 warmups, 20 requests (sec)
bench.php - 100 consecutive runs, 10 warmups, 2 requests (sec)
|
AWS x86_64 (c7i.24xl)
Laravel 12.11.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)
Symfony 2.8.0 demo app - 100 consecutive runs, 50 warmups, 100 requests (sec)
Wordpress 6.9 main page - 100 consecutive runs, 20 warmups, 20 requests (sec)
bench.php - 100 consecutive runs, 10 warmups, 2 requests (sec)
|
|
Pretty cool, I was able to confirm the ~3% improvement for the Laravel default page locally. They use a lot of closures, and this patch is able to avoid instantiation for 2384 out of 3637. |
This comment was marked as off-topic.
This comment was marked as off-topic.
96ca7fa to
f9927b1
Compare
This field is still unused on master, but required for GH-19941.
f9927b1 to
4451788
Compare
| @@ -0,0 +1,46 @@ | |||
| --TEST-- | |||
There was a problem hiding this comment.
this tests a bunch of cases that are not inferred as static, can we also test a few that are inferred as static?
There was a problem hiding this comment.
ext/reflection/tests/closures_005.phpt contains some rudimentary ones, but I'll add some more standard cases that can be inferred in a new test.
|
|
||
| Deprecated: ArrayObject::__construct(): Using an object as a backing array for ArrayObject is deprecated, as it allows violating class constraints and invariants in %s on line %d | ||
| object(ArrayObject)#3 (1) { | ||
| object(ArrayObject)#%d (1) { |
There was a problem hiding this comment.
the changes in this file to replace hard-coded numbers with %d are fine, but unrelated to the actual functionality change - can you send a separate PR if you want to adjust these? I'd be happy to review that, but it makes this PR bigger than it needs to be. Applies to a few other files too
Also not all of the object ids were updated with placeholders, just some
There was a problem hiding this comment.
I think the object IDs have changed as they are declared earlier - at compile time. I personally would like to keep hardcoded numbers to be able to review possible changes of the declaration order earlier in the future.
| var_dump( | ||
| (new class { | ||
| function test(){ | ||
| return (function(){ return compact('this'); })(); |
There was a problem hiding this comment.
shouldn't this maintain the non-static-ness of the function, given the access to $this via compact()? I'd say that any use of compact() should be treated the same as $$var - if we aren't going to analyze the possible values to make sure $this is not included, then we should be conservative and not infer static
There was a problem hiding this comment.
I think there are much more cases. Like uasort($arr, 'Foo::bar'). IMO they are not worth to be pursued.
There was a problem hiding this comment.
Yeah you're both correct. compact() needs to be excluded, and so do any calls to internal functions using Z_PARAM_FUNC(), i.e. those that accept callable, and are passed something that could be a string. I'll think about how to best address those cases.
|
|
||
| class Test { | ||
| public function method() { | ||
| // It would be okay if this is NULL, but the rest should work |
There was a problem hiding this comment.
this comment needs some update, now it is NULL
| public $a; | ||
|
|
||
| public function x() { | ||
| $a = &$this; |
There was a problem hiding this comment.
should this be marked as static? Doesn't it give access to $this via the closure?
There was a problem hiding this comment.
$this is not the scope, but a bound variable. This PR does two things, it "infer static" and "cache not bound closures". The case here allows the 1st one but not the 2nd one.
So I expect the l31 unchanges and prefer to keep it hardcoded.
| array(1) { | ||
| [0]=> | ||
| object(Closure)#%d (4) { | ||
| object(Closure)#%d (%d) { |
There was a problem hiding this comment.
same as closure_20, don't these still need $this ?
| zend_error(E_WARNING, "Cannot bind an instance to a static closure, this will be an error in PHP 9"); | ||
| return false; | ||
| } else { | ||
| *newthis_ptr = NULL; |
There was a problem hiding this comment.
got confused reading this for the first time wondering why no warning was needed here - can I suggest adding some comment explaining this with a reference to the RFC, which specified that
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.
RFC: https://wiki.php.net/rfc/closure-optimizations
This PR implements
staticinference for closures, with some restrictions. The closure must not:$this. That's the obvious case.$$var, given$varcould be'this'.Foo::bar(), given this could be a hidden instance call to a (grand-)parent method.$f(), for the same reason as 3.call_user_func(), for the same reason as 3.$thisflows from parent to child.require,includeoreval, given the called code might do any of the above.In a Symfony Demo run specifically, static inference works for 68/87 (~78%) closures that were explicitly marked as
staticby Symfony. That seems quite decent.The PR also adds caching for
staticclosures that don't have any bindings and don't declarestaticvariables. Instances of such closures can be re-used almost without side-effects (except for object identity).For Symfony Demo (with
staticremoved from all closures), I measured an improvemend of ~0.1%, so definitely not very significant. Synthetic benchmarks can improve quite a bit, though this will also apply to real-world code that creates closures in loops.improves by ~78% in my test runs.
If persistent objects are ever implemented, the instantiation could be done fully at compile-time.