[00:02] to clean up memory. But when I ask PHP developers, they usually cannot explain how it works in detail. This is not a huge problem because PHP's share nothing memory model combined with the garbage collection usually cleans up everything [00:17] automatically in a very nice fashion. However, sometimes you can run into specific problems in your application where a more detailed understanding of garbage collection would definitely help. In this video, I want to take a [00:31] PHP 8.5 improvement to the garbage collector as an example to discuss the garbage collection mechanism in detail. Mo, I am Benjamin and I help developers with PHP performance. My colleague Tobias wrote an article about this PHP [00:46] 8.5 improvement to the garbage collector. It was contributed by Ilia and it's titled mark enums and static fake closures as not collectible. And this is uh a rare improvement to the garbage collector in um the last years. [01:02] So one of the biggest improvements in uh the garbage collection happened in the 7 uh life cycle of PHP. So it's already quite some time ago. To understand this pull request, we first need to understand garbage collection in PHP a [01:18] little bit more. So let's make a little quick detour and try and play a little bit with how garbage collection in PHP works. Let's start with a simple example. We have a class A. It's allocated here to a variable and then we [01:32] explicitly control the garbage collector which is possible using a few PHP which is possible using a few PHP functions. So we can say um collect cycles. So the cycle collector is the process that checks all the variables [01:47] that are in memory and looks if they can be garbage collected and the memory be be garbage collected and the memory be freed. So in this case here um we run freed. So in this case here um we run this um program and it outputs the [02:01] contents of the GC status function which um as it says here in the tool tip gets information about the PHP garbage collector. So what it does it shows how collector. So what it does it shows how often was it run um and uh also how many [02:18] um variables were were collected when it ran. So in this case here, the garbage ran. So in this case here, the garbage collector is um triggered with this function, but because other variables are in use, um there's not really [02:31] are in use, um there's not really anything to do. Um the uh this means that the the garbage collector didn't actually run because it saw up front that it has nothing to do. Um and um [02:47] also didn't collect any variables. Why didn't didn't it have anything to do? The reason for this is that um PHP optimizes a lot away here. It sees this optimizes a lot away here. It sees this is never used and um already like runs [03:01] is never used and um already like runs and compiles it in a way that um the variable is cleared and cleaned up and nothing ever happens with that. So that is a very simple case of PHP memory and the more complex cases are where the [03:16] garbage collection comes into place. So for simple cases when PHP can count how often the variable is used, it counts when it's not used anymore. It goes to zero. It cleans it up. Um the garbage collector is only necessary for cases [03:31] where objects are using cyclic references. What does it mean? It means that two objects, for example, A and B here point at each other in um in a [03:43] circular way. And what happens in that case is if we again instantiate this here with a we run PHP cyclic references [04:01] run the collector and then we look at the stats again. So in the first case we the stats again. So in the first case we have no runs. We have two roots and roots are um sort of objects and arrays at the root of um sort of a tree of [04:15] at the root of um sort of a tree of objects or tree of arrays. So that means objects or tree of arrays. So that means um this is the um the variables that the garbage collector or the cycle collector looks at and attempts to find out if [04:28] they can be cleaned up. So what happens in the second case? So we triggered the garbage collector. We see it ran once. It it collected two see it ran once. It it collected two variables. Uh it collected the variables [04:42] that pointed at each other in cyclic references here. Because cyclic references never make the um the counting of references go to zero. Only the garbage collector can actually trigger a cleanup of this. And then we [04:57] can see like uh how much time was spent in in the garbage collector here as well. So what is the case now that the PHP 8.5 improvement works on. So the next step we need to look at is how efficient is the garbage collector. So [05:12] we've seen collecting only two objects in a cyclic way. Um that is not really problematic. It runs very fast. Um, this will actually not trigger at a uh usual [05:24] runtime of a PHP process and PHP will just rely on the shutdown of the process to make that work. Um, the garbage collection in PHP will trigger collection in PHP will trigger automatically once the number of roots [05:39] so we see the roots in the statistics here. Um, goes over the threshold. Uh once that happens, the garbage collection is triggered automatically. So it's not triggered via function by yourself. It will trigger automatically [05:55] and it will then um clean up the variables that it can clean up. Um however what happens if we put these Um however what happens if we put these cyclic references in a list create more [06:10] than 10 10,000 of them uh and then the automatically. Let's see how this works out. So running the threshold script what it will do you can see um we have 20,000 roots available at [06:27] that point here still um the threshold is not 10,000 anymore but it increased is not 10,000 anymore but it increased itself to 40,000 and we see only two variables have been collected here um so the other two um sort of like the 20,000 [06:43] that we still have here are not collected it. So if we go and unset the collected it. So if we go and unset the list [06:55] and run this again, we see a different picture. We see that we see a different picture. We see that we had more um uh 40,000 variables are now collected from the 20,000 roots that we had. So, it's a much bigger [07:08] improvement that happened here because the list doesn't hold a reference to all the list doesn't hold a reference to all the um cyclic um combinations of A and B here anymore. The cycle collector can run and clean them up. [07:23] So, what we can see however here and this is what ties it to the PHP 8.5 improvement is that we can see the collector time is actually noticeable. collector time is actually noticeable. So, it takes 1.4 4 milliseconds for just [07:36] those 40,000 objects and cleaning them up, checking if they can clean can be cleaned up. So the number of roots that we have in the cycle collector affects how long it needs to check. It also means that [07:51] whenever the 10,000 thresholds is reached and the garbage collection then runs and cannot clean up, it will increase the threshold to a by a bigger increase the threshold to a by a bigger number uh 10,000 uh then 20,000 40,000 [08:05] and will always double the amount and that means for every time it checks it will need to check more and more objects and that can get slower. And this is and that can get slower. And this is where the PHP 8.5 improvement um ties [08:19] where the PHP 8.5 improvement um ties into. Let's go back and uh take a look. So the title of the improvement is mark enams and static fake closures as not collectible. Um and from our sort of quick understanding that we now had um [08:33] quick understanding that we now had um the garbage collector checks if it can clean up objects what we call roots and the statistics also says roots and um what this pull request now does is it automatically knows that um a specific a [08:50] automatically knows that um a specific a specific variable that is in memory um is not collectible. So you can specify an object should not be part of the garbage collection process because we already know that it cannot actually [09:05] have cyclic references. It cannot be part of cyclic references and without being part in cyclic references it would just be wasteful to look if this object uh needs to be collected at this point or not. So the pull request is super [09:20] simple. Uh we can look at the code at two places. So once when a fake closure two places. So once when a fake closure and once when a an enum is created, it and once when a an enum is created, it adds an additional flag not collectible. [09:33] adds an additional flag not collectible. And the PHP engine uses this to make sure that a not collectible object is also not part of the list of objects to clean up. What does it do for a difference? You can see this um code [09:45] difference? You can see this um code example from Ilia here. He creates um example from Ilia here. He creates um static fake closures. So this is the syntax to create a closure of a PHP internal function or a PHP function [09:59] internal function or a PHP function using the three uh double um points. It creates an array of 10 million closures to the fu function and then iterates them and uh outputs the garbage collection status. And then before this [10:15] change what happens is that the threshold of to collected object keeps increasing and increasing. Uh nothing is actually increasing. Uh nothing is actually collected uh um in any point and um the [10:30] runtime of the garbage collector being triggered 44 times automatically is triggered 44 times automatically is actually um 200 milliseconds of garbage collection time. However, um as I mentioned before, um [10:44] the static fake closures, they are never part of a cyclic reference. So checking them for cleanup uh doesn't actually make sense. With this improvement um we saw before, we now see that the garbage collection is never triggered because uh [11:02] the number of roots never increases 10,000 anymore because the static fake closures are not included in there. And that also means that we have no time spent in the collector and the total runtime of the script is much faster. So [11:17] what does this kind of change to the garbage collector improve? It uh improves the like it reduces the pressure of the garbage collector problem that can happen in PHP applications. Um if you're using a lot [11:33] of objects, a lot of arrays, um then this increases the number of um then this increases the number of roots in the um that are potentially necessary to be collected by the uh garbage collector. And [11:49] when you increase them, the garbage collection is more likely to trigger. And if it's more likely to trigger, then it can mean that it's also um more [12:01] likely to not be efficient because there are not really cyclic references that we can remove at this point of time. And by creating more and more objects, you increase the likelihood of the garbage collector to run. And with this um [12:17] change here that Ilia did, it reduces the pressure on the garbage collector because it doesn't actually count these variables as part of the garbage collection process. And this overall improves the performance because [12:31] garbage collection runs uh less often. Um it doesn't need to take these variables into account whenever it triggers. And in uh overall for applications using static fake closures and enomes this should be an [12:45] improvement. We are not done here though. The question is what are static fake closures? Um so what code is actually considered to be improved here? Uh let's take a look. So um a fake closure is as the name says [13:03] not not really a closure. It's a callable but um the engine has different callable but um the engine has different ways of creating fake closures and static fake closures are a special edge case of that. You can see here uh [13:19] in this code example what is a static fake closure. For example, if you use a fake closure. For example, if you use a PHP internal or a userland function and PHP internal or a userland function and using the um three dot operator, this [13:31] creates static fake closures. The same for doing that for a static method on a for doing that for a static method on a class. Also, if you use the closure from callable on a function, that will also create a static flow, a fake closure [13:45] from the engine perspective. So, what is not a static fake closure? Um, so a fake closure is if you use the three dot operator on a dynamic method. [13:57] So, not a static method of a class, but a dynamic method. This is a fake closure but not static. And then um a static function or a static closure um is is [14:09] actually not a fake closure. It's a real closure. So if you use this syntax as a closure. So if you use this syntax as a real closure, not um um a fake closure. So this optimization is only for these cases. So if you're using this syntax um [14:23] cases. So if you're using this syntax um a lot uh in your codebase for different callables and passing callables around then um the memory pressure of the garbage collection is reduced for these cases. The second case that we want to [14:37] cases. The second case that we want to look at as part of this um uh change is look at as part of this um uh change is enomes and enomes are also not possible to have cyclic references. So you see here the declaration of an enome is uh [14:50] here the declaration of an enome is uh very straightforward. It maps to a PHP class or an object. So if we um use the enum here we have two instances of objects in place and previously these objects uh were considered roots. So [15:06] they were part of the garbage collection um uh calculation. Uh however um you cannot actually assign dynamic properties to them. So let's run [15:18] um this example code here and we immediately get a fatal error because you cannot create a dynamic property on fu and without being able to create a [15:30] dynamic property to point the enum to an object and back to something else. We cannot create a cyclic reference and without a cyclic reference um it it collection process. [15:44] uh an enum that is instantiated is available and it will be cleaned up at shutdown of the PHP process not earlier. This was a quick rundown of a change to the PHP 8.5 garbage collector. Uh I tried to use it as an example to explain [16:00] how garbage collection works in PHP roughly um using two code examples and um trying to um explain how pressure on the garbage collection and cycle [16:13] collection can increase the runtime of a PHP process by triggering the garbage collection over and over again. and the garbage collector not being able to clean anything up. If you like my PHP performance content, please subscribe to [16:27] this channel or to our newsletter. The link is in the description. Bye.