PHP memory allocation, or how to use unset(), or why to use for() instead of foreach()
Sometimes you’ll need to clean up the memory used by PHP. Especially if you have long running CLI scripts this will make sure to avoid the “PHP Fatal error: Allowed memory size of N bytes exhausted”. The basic idea is to remove unused variables from memory by using PHP’s unset() command.
In it’s simpliest form, unset() will look like this:
<?php php $i = "is now set"; echo $i; unset($i); // remove $i from memory, i.e. tell the garbage collector that it may unallocate $i ?>
Internally, PHP works more or less like Java when it comes to unsetting variables. Thus, unset() will not immediately unset the corresponding variable. Instead, it simply tells it’s garbage collector that the variable is no longer needed and may now be removed from memory. Good news are that the garbage collector now works quite smart and usually you don’t have to take care of how it works. Bad news are, that there are still some cases when the garbage collector fails.
I’ve just stumbled uppon the following problem:
<?php
class Foo
{
public $id = 0;
public $var1 = "this is a looooooong string";
public $var2 = 102980912098;
public $var3 = "another meaningless but loooooong string";
public function __construct($id) {
$this->id = $id;
}
}
// init an array of 30.000 Foo-Objects
$fooarray = array();
for($i=0;$i<30000;$i++) {
$v = new Foo($i);
$fooarray[] = $v;
}
$i=0;
// now, iterate over the array
foreach($fooarray as $k => $v) {
// do something meaningful with $v
unset($v);
unset($fooarray[$k]);
if ($i++ > 1000) {
echo "Memory allocated: ".number_format( memory_get_usage())."\n";
$i=0;
}
}
?>
This script simply inits an array of 30.000 objects, iterates over them and (within the iteration) unallocates the parts of the array which are no longer needed. Surprisingly, the output of this script will look like this (tested with PHP 5.3.3):
Memory allocated: 15,474,664 Memory allocated: 15,426,584 Memory allocated: 15,378,488 Memory allocated: 15,330,392 Memory allocated: 15,282,296 Memory allocated: 15,234,200 Memory allocated: 15,186,104 Memory allocated: 15,138,008 Memory allocated: 15,089,912 Memory allocated: 15,041,816 Memory allocated: 14,993,720 Memory allocated: 14,945,624 Memory allocated: 14,897,528 Memory allocated: 14,849,432 Memory allocated: 14,801,336 Memory allocated: 14,753,240 Memory allocated: 14,705,144 Memory allocated: 14,657,048 Memory allocated: 14,608,952 Memory allocated: 14,560,856 Memory allocated: 14,512,760 Memory allocated: 14,464,664 Memory allocated: 14,416,568 Memory allocated: 14,368,472 Memory allocated: 14,320,376 Memory allocated: 14,272,280 Memory allocated: 14,224,184 Memory allocated: 14,176,088 Memory allocated: 14,127,992
If you replace the last part of the script with this…
for($k=0,$c=count($fooarray);$k<$c;$k++) {
$v=$fooarray[$k];
// do something meaningful with $v
unset($v);
unset($fooarray[$k]);
if ($i++ > 1000) {
echo "Memory allocated: ".number_format( memory_get_usage())."\n";
$i=0;
}
}
…and run the script again, the output will look like this:
Memory allocated: 13,535,336 Memory allocated: 13,118,520 Memory allocated: 12,701,664 Memory allocated: 12,284,832 Memory allocated: 11,867,992 Memory allocated: 11,451,160 Memory allocated: 11,034,328 Memory allocated: 10,617,496 Memory allocated: 10,200,656 Memory allocated: 9,783,824 Memory allocated: 9,366,992 Memory allocated: 8,950,152 Memory allocated: 8,533,320 Memory allocated: 8,116,488 Memory allocated: 7,699,656 Memory allocated: 7,282,824 Memory allocated: 6,865,976 Memory allocated: 6,449,144 Memory allocated: 6,032,312 Memory allocated: 5,615,480 Memory allocated: 5,198,648 Memory allocated: 4,781,816 Memory allocated: 4,364,984 Memory allocated: 3,948,152 Memory allocated: 3,531,320 Memory allocated: 3,114,488 Memory allocated: 2,697,656 Memory allocated: 2,280,824 Memory allocated: 1,863,992
Obviously, using a foreach loop will cause problems when it comes to unallocating parts of the array you iterate over within the foreach.
Related posts:

CSS Zend Garden
KingCrunchs kleine Welt
Unfortunately I have to disagree with your conclusion.
The foreach creates a copy set of the array pointer, temporally incrementing the reference count of each array element by one.
That means that the RAM is released by the GC once the foreach terminates. Test it yourself: Add a final
echo “FINAL Memory allocated: “.number_format( memory_get_usage()).”\n”;
after your foreach.
Thanks for your input, Zsolt. Actually, I do not see any objection between my post and your remark. The problem in my case was that I ran into an exception within the FOREACH and somehow I had to solve the problem. Thus, even if the memory will be released after the FOREACH it doesn’t help if you get an error within the foreach.