PHP 8.5 Garbage Collection Improvements
Big optimizations in PHP’s memory consumption have become exceedingly rare. Since then, memory improvements have been smaller in scope, focusing on little details for certain types of variables.
Like improving the Garbage Collector (GC) in edge cases, which Iljia Tovilo contributed to PHP 8.5 titled “Mark enums and static fake closures as not collectable”.
In short, this means that for these two variable types, the cycle collector will not attempt to “collect” them, preventing unnecessary runs if lots of those instances are in use.
This does not come with any downsides, as these types cannot be cyclic and are therefore not subject to the GC anyway.
Quick Detour: How does PHP’s Garbage Collector work?
To understand why this is useful, we should remind ourselves quickly how garbage collection in PHP operates. For more details, you can take a look at our blog post and podcast episode on the topic.
In short, PHP’s GC does not run continuously but is triggered by a threshold. The threshold being that there are currently at least 10,000 possibly cyclic objects or arrays in memory. If the number of these instances increases without the possibility to clean up, the GC might be running over increasingly large numbers of objects, consuming valuable computational resources.
As a result, reducing the number of objects eligible for garbage collection reduces the chance of GC runs and therefore improves performance.
One neat function, added in PHP 7.3, was not mentioned in our blog post. With gc_status() (Docs) it is quite easy to gain insights into garbage collection performance.
What do we actually gain?
Looking at the example from the pull request, we can see that plenty of instances of first class callables are added to and iterated over afterward.
With older PHP versions, this will trigger the cycle collector to check all of those instances for cyclic references. Since these instances cannot have cyclic references, these checks are unnecessary. With 10 million entries, this resulted in 44 runs, while anything from PHP 8.5 onwards has 0 runs with the same code.
Definition: “static fake closures”
The pull request’s title states that the optimization applies to “enums and static fake closures”. Since static fake closures are definitely the more relevant case and the terminology does not match userland namings, let’s dive into this: what are “static fake closures”?
The example (see last chapter) does use “static fake closures”, which are commonly called “first-class callables” in userland. In this example, this means foo(...).
To determine if a callable is a “fake closure”, you can run a check if the anonymous flag returns false on the callable’s reflection class, i.e., (new \\ReflectionFunction($callable))->isAnonymous();.
The “static” part of the type refers to what we understand in OOP as static: not having access to an object instance (i.e., $this).
In this list of different closures, you can see which ones are considered static fake closures by the PR and which are not:
The Enum Case
The second type that was optimized are instances of Enum. This will most likely have relatively minimal effects in real projects, as this would only impact performance if a codebase contains many Enums with many cases. Enum instances are singletons, so each Enum’s case is instantiated exactly once. This would not have any effect if there are thousands of references to the same three or four enum cases, since each enum case can only appears once in the GC’s root buffer.
Conclusion
Tiny wins like these show that PHP’s memory management can still be improved while already being quite mature.
This change might improve performance in some cases, but most importantly, it makes sure that garbage collection runs do not waste time on irrelevant checks.
Thanks for the contribution, Iljia Tovilo.
Time to get started with Profiling! With our free trial.
PHP 8.5 Garbage Collection Improvements for Enums and Closures