Performance Impact of the PHP Garbage Collector

By Fabien Potencier, on Dec 08, 2014

Last week, Composer, the dependency manager used by modern PHP applications, announced that its latest version was between 30% and 90% faster than its previous version. And this huge performance improvement was not because of a big refactoring, quite the contrary. First, Nils wrote a patch to lower the number of function calls and that led to a significant speed improvement. But Jordi also disabled the circular reference garbage collector in a later patch and that increased the performance of Composer drastically for most of us.

So, Composer got this huge performance boost just by changing a single line of code:

// src/Composer/Installer.php
namespace Composer;

class Installer
{
    // ...

    public function run()
    {   
        gc_disable();
        // ...
    }
}

Even if the patch is very small, the good consequences are not very obvious and rather counter intuitive at first. In this post, I won’t write about the theory as Anthony Ferrara wrote a very detailed blog post about how garbage collecting works in PHP.

Surprisingly enough, and until last week, nobody ever thought about trying to optimize Composer that way and that’s probably because no PHP tool is able to tell you what the garbage collector does during the execution of your code. When optimizing the performance of an application, you need good tools that tell you the true story on what’s going on behind the scene. Blackfire Profiler is such a tool, and like any other ones, it was unfortunately unable to tell you why Composer was slow.

Is it even possible to get useful information about the garbage collector from PHP? Well, as it turns out, it’s not easy. The Blackfire team worked hard last week to find ways to gather relevant information about the PHP garbage collector and today, I’m very happy to announce the immediate availability of this feature in the latest version of our stack. If you upgrade Blackfire today, you will get all the information you need to know about the PHP garbage collector calls. You can now decide if disabling the garbage collector will help your code run faster or consume less memory without shooting in the dark; remember that calling gc_disable() is not something you want to do in all your PHP scripts.

Analyzing Composer

Let’s see what the new version of Blackfire can tell us about the Composer performance improvement. First, I ran composer install in a project with quite a few dependencies (symfony.com) with a Composer binary that was created before the code optimizations from last week:

blackfire-composer-before

As you can see, the garbage collector was triggered during the call of multiple functions and the time spent in it represents more than 34% of the total execution time. This is actually the biggest cost in the profile.

The “@Garbage Collector” node details shows that it has been called 244 times:

blackfire-composer-before-info

More interestingly, we can see that garbage collection did not free any memory (+ 300Kb):

blackfire-composer-before-info-memory

So, the garbage collector ran for 35 seconds without being able to free any memory; those 35 seconds are just lost for nothing.

Then, I ran it again with a Composer binary created after the code optimizations. To better visualize the difference, I’ve used the Blackfire comparison feature. As you can see, the story is totally different:

blackfire-composer-after-summary

As expected, disabling the garbage collector reduced the time it took for Composer to run by 35 seconds. Summed with the other optimizations made on Composer, the total execution time is reduced by more than one minute (from more than 2 minutes to 40 seconds.) More important, Blackfire told us exactly what was going on and we were able to understand the impact of PHP garbage collector in this specific setup.

Analyzing a Simple (Leaking) PHP Script

Now, let’s take another example where the story is different:

$i = 15000;

function cycle()
{
    $a = (object) array();
    $b = (object) array();
    $b->a = $a;
    $a->b = $b;

    return $a;
}

while (--$i) {
    $b = cycle()->b;
}

Blackfire tells us that the PHP garbage collector ran 3 times (representing 21.4% of the total execution time), and it was able to collect 10Mb of memory back. So, our script did not leak thanks to the garbage collector.

If we disable the garbage collector by adding a gc_disable() call at the beginning of the script and compare the two profiles, here is the result:

blackfire-leak-comparison

As expected, the memory usage is much higher without the garbage collector (+7.71Mb), but the execution time is “better” (-7.35ms). So, this is a trade-off between execution time and memory.

It’s your turn now. If you want to profile some Web applications or some PHP CLI tools and don’t have Blackfire installed yet, go to blackfire.io website, sign up and install the required software as explained in the Getting Started tutorial.

Happy profiling!

Fabien Potencier

Fabien Potencier is the CEO and founder of Blackfire.io. He founded the Symfony project in 2004 as he constantly looked for better ways to build websites. Fabien is also the creator of several other Open-Source projects, a writer, a blogger, a speaker at international conferences, and the happy father of two wonderful kids.