Where to find the "low memory" and "free CPU cycles" calls triggering garbage collection on unset()?
Asked Answered
C

1

7

I often find references to the following quote being used when explaining that a PHP unset() doesn't trigger "garbage collection" immediately, but only when it sees fit (emphasis mine):

unset() does just what it's name says

  • unset a variable. It does not force immediate memory freeing. PHP's garbage collector will do it when it see fits - by intention as soon, as those CPU cycles aren't needed anyway, or as late as before the script would run out of memory, whatever occurs first.

If you are doing $whatever = null; then you are rewriting variable's data. You might get memory freed / shrunk faster, but it may steal CPU cycles from the code that truly needs them sooner, resulting in a longer overall execution time.

I want to know how the C code for this "low memory" and "free CPU cycles" triggering of the garbage collection works exactly and whether it differs between PHP 5.2.x and PHP 5.3+.

So I downloaded the C source files of PHP 5.2.17 and tried to locate the proper code sections. Maybe I'm just blind, or my C skills are too low, but I failed to find such code.

Can somebody point me to the proper C files, please?

EDIT:

While searching for example usages of the above quote, I realized something weird.

Some of the examples, like https://mcmap.net/q/108690/-what-39-s-better-at-freeing-memory-with-php-unset-or-var-null, reference to this quote by using the following URL to a comment on php.net: https://www.php.net/manual/en/function.unset.php#86347.

Browsing to this URL only shows the top of the unset() manual. Comment #86347 is missing.

Checking the wayback machine shows, that this comment DID exist from Oct 2008, but vanished sometime in or after Sep 2012 (reason unknown).

Maybe that quote is, and always was, just plain wrong?

Or is there anybody out there, who can point me to the proper C files?

Cotter answered 27/11, 2013 at 0:14 Comment(8)
I'd love to know how the garbage collector actually decides to work. What the true triggers are.. Because 'will do it when it sees fits' is not self-explanatory. +1Wells
i believe this is where you want to be looking: lxr.php.net/xref/PHP_5_2/main/alloca.cMucilaginous
Where did you read that quote? What they're describing doesn't make sense.Disapprove
@duskwuff: here for example: https://mcmap.net/q/108690/-what-39-s-better-at-freeing-memory-with-php-unset-or-var-null/693207Cosmorama
@Dagon: thanks, but I fail to find any "low memory" or "free CPU cycles" code in that file, too. Which lines should it be?Cosmorama
I can't find the quoted text anywhere in the page that answer is referencing. It's possible the documentation has changed.Disapprove
@duskwuff: yeah, I realized that, too. Updated my question.Cosmorama
If it's written in the user notes you should always assume it to be wrong, unless you have strong evidence to believe otherwise ;)Laodicean
D
6

OK, so it's time for some PHP Mythbusters! Please start by reading the PHP documentation on how garbage collection works, as I'm going to assume some prior knowledge of how this all works:

The second document specifically explains what triggers the running of the cyclic garbage collector. It has nothing to do with "free CPU cycles" or "low memory" — it's based entirely on the number of potential garbage objects that are present:

When the garbage collector is turned on, the cycle-finding algorithm as described above is executed whenever the root buffer runs full. The root buffer has a fixed size of 10,000 possible roots.

That is, the cyclic garbage collector runs any time that a certain number of potentially garbage objects have accumulated, irrespective of the size of those objects. Reviewing the code in zend_gc.c confirms this — there is certainly nothing in there checking the overall amount of free memory, and there's certainly none of the threading that would be needed to make the GC run while the CPU is free. So I think we can call this part "busted".


Next, let's take a look at what the actual difference between $x = null and unset($x) might be. First, let's confirm that they do the same thing using this class as our Buster the Test Dummy:

class NoisyDestructor {
    function __destruct() {
        print "Destructor called\n";
    }
}

Now, let's see what the difference between setting a variable to null and unset()-ing it is:

$x = new NoisyDestructor();
print "Created\n";
$x = null;
print "Nulled\n";

print "\n";

$x = new NoisyDestructor();
print "Created\n";
unset($x);
print "Unset\n";

When we run this, we see:

Created
Destructor called
Nulled

Created
Destructor called
Unset

Wait a minute — that's the exact same sequence for both! There's no functional difference between the two. Now, how about the performance?

class Thing { }

$start = microtime(true);
for ($i = 0; $i < 1e6; $i++) {
    $x = new Thing();
    $x = null;
}
printf("%f sec for null\n", microtime(true) - $start);

$start = microtime(true);
for ($i = 0; $i < 1e6; $i++) {
    $x = new Thing();
    unset($x);
}
printf("%f sec for unset\n", microtime(true) - $start);

Now, using my laptop to test, with PHP 5.4, I get:

0.130396 sec for null
0.175086 sec for unset

Not only is the performance difference between setting a variable to null and unsetting it pretty minor, considering how many times we had to run this loop to see this result, but it's actually the exact opposite of what that comment claimed: unset() is about 25% slower! This PHP myth has been well and truly busted.

BUSTED

TL;DR: The quote you found is completely wrong. (It seems likely that it was removed from PHP.net for this exact reason.)

Disapprove answered 29/11, 2013 at 22:50 Comment(3)
The reason you're seeing the timing difference between null and unset is that you tested in the global scope. In function scope the difference will be a lot smaller. The global scope makes use of a symtable, from which the unset code will continuously remove and add buckets (which is slow). In the local scope there will be no symtable and everything is handled via the CV table, so only the zval will need to destroyed and created, which happens in about the same way for both codes.Laodicean
@duskwuff Just one more question: zend_gc.c was introduced in PHP 5.3 (iirc). How does the GC work in PHP 5.2? Is it different to PHP 5.3+?Cosmorama
There's no garbage collector in PHP 5.2, so cyclic references never get collected. There's still reference counting, though, so most objects will still get collected when they are no longer referenced.Disapprove

© 2022 - 2024 — McMap. All rights reserved.