Which of the following is best for memory usage? - php

Which of the following is best for memory usage?
$a = new foo();
$username = $a->getuserName('username');
$b = new bar();
$b->validateUserName($username);
//OR
( new bar() )->validateUserName( (new foo() )->getuserName($username) );
Does storing an object in a variable increase memory usage?
does PHP treats other languages differently?
Someone can explain the management of PHP memory (should and should not)

To test memory usage you can use the next template:
<?php
$mem = 0; // create variable before "memory_get_usage()";
$mem = memory_get_usage();
// Place your code here
$mem = memory_get_usage() - $mem;
print ("Memory used: $mem");
For example I created the stub:
<?php
class foo {
function getuserName($a) {
return $a;
}
}
class bar {
function validateUserName($a) {
}
}
$mem = 0; // Allocate variable before "memory_get_usage()";
$mem = memory_get_usage();
$a = new foo();
$username = $a->getuserName('username');
$b = new bar();
$b->validateUserName($username);
$mem = memory_get_usage() - $mem;
print ("Memory used: $mem\r\n");
result on php 5.6.40 win x64 is 560 bytes.
If I change the code to your second variant
...
$mem = 0; // Allocate variable before "memory_get_usage()";
$mem = memory_get_usage();
// Note I've changed $username to 'username'
(new bar())->validateUserName( (new foo())->getuserName('username') );
$mem = memory_get_usage() - $mem;
print ("Memory used: $mem\r\n");
Now result is 0 bytes.
Let's see another variant:
...
function test() {
$a = new foo();
$username = $a->getuserName('username');
$b = new bar();
$b->validateUserName($username);
}
$mem = 0; // Allocate variable before "memory_get_usage()";
$mem = memory_get_usage();
test();
$mem = memory_get_usage() - $mem;
print ("Memory used: $mem\r\n");
Memory used: 64
Let's change the function to the second variant:
...
function test() {
(new bar())->validateUserName( (new foo())->getuserName('username') );
}
...
Now result again is 64 bytes.
And one more test:
...
$mem = 0; // Allocate variable before "memory_get_usage()";
$mem = memory_get_usage();
$a = new foo();
$username = $a->getuserName('username');
$b = new bar();
$b->validateUserName($username);
unset($a);
unset($b);
unset($username);
$mem = memory_get_usage() - $mem;
print ("Memory used: $mem\r\n");
Here result is 0 again.
From there I can assume, when you're creating global variables, they remains all the time, and object they are referencing cannot be garbage collected, so they remain in the memory. As soon as you unset variables, or declare them in a particular scope (function), or do not declare them at all - there is no difference in result. To see when object are destroyed and memory is freed you can add desctructors in the classes:
...
function __destruct() {
print "foo/bar destroyed\r\n";
}
...
Still, writing calls in one single line does not allocate unnecessary variables at all, thus improving execution time.
But my recommendation: do not bother with such optimizations in a language such as php. The language itself is not that fast and not that memory effective. For example, adding each item into array may cost more than 100 bytes of memory. Sparing several bytes by making code less readable is not worth it.

Related

PHP - Slow performance on function return assignment

As part of a project i came across this situation where inside a loop, i store the value of a function return.
This happened to be a bottleneck for the application, where big arrays would take forever to be processed.
To me, the assignment should be no reason for the incredible slow performance.
On the other hand, the same function call, with no assign on return gives much better performance.
Can you explain me why the first loop is much slower?
Output:
First took 1.750 sec.
Second took 0.003 sec.
class one {
private $var;
public function __construct() {
$this->var = array();
}
public function saveIteration($a) {
$this->var[] = $a;
return $this->var;
}
public function getVar() {
return $this->var;
}
}
$one = new one();
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$res = $one->saveIteration($i);
}
echo "First took " . number_format(microtime(true) - $time_start, 3) . " sec.";
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$one->saveIteration($i);
}
$res = $one->getVar();
echo "<br>Second took " . number_format(microtime(true) - $time_start, 3) . " sec.";
According to http://php.net/manual/en/functions.returning-values.php#99660 the array return value is not passed by-reference but by-value. Which means that a copy of the array is created (at the very least, when you change it again), which in turn needs time to actually create the copy (allocate memory, memcopy the data).
It probably have something to do with the fact that you're creating 10.000 arrays; each time incrementing the number of elements of the new array by 1 element.
My guess while you're inside the loop the local variable isn't freed on its own; Therefore I went ahead & tried freeing it using unset which got the results very close.
I know this is not a real world example; but in your code if you have something similar you could get away with it by just freeing (unsetting) the local variable once you're finished with it
here's your test code again:
class one {
private $var;
public function __construct() {
$this->var = array();
}
public function saveIteration($a) {
$this->var[] = $a;
return $this->var;
}
public function getVar() {
return $this->var;
}
}
$one = new one();
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$res = $one->saveIteration($i);
unset($res);
}
echo "First took " . number_format(microtime(true) - $time_start, 3) . " sec.".PHP_EOL;
$time_start = microtime(true);
for ($i = 0; $i < 10000; $i++) {
$one->saveIteration($i);
}
$res = $one->getVar();
echo "Second took " . number_format(microtime(true) - $time_start, 3) . " sec.".PHP_EOL;
Note: the only thing I've modify is adding unset in the first example
Result:
First took 0.068 sec.
Second took 0.062 sec.
#Jakumi made an excellent point. Since the values must be copied when you assign, 10,000 extra operations and a lot more memory are required in the first loop.
The difference between the two loops is actually much greater than your testing shows. Your comparison would be more fair if between the two tests, you reset with:
unset($one); $one = new one();
In your current test, the second loop is being executed while still holding the large array from the first loop in memory, so your results are not independent. See this modification

Reference Counting in php - how does it work?

I am trying to understand this article "PHP Manual -> Features -> Garbage Collection"
unfortunately few things are unclear for me.
1.
To avoid having to call the checking of garbage cycles with every
possible decrease of a refcount, the algorithm instead puts all
possible roots (zvals) in the "root buffer".
but what in case
<?php
$a = new \stdClass(); (1)
$a = new \stdClass();
Then I guess the first object become "lost" zval like
no_symbol : (refcount=1, is_ref=1) = stdObject
Will such "lost" zvals be added into root buffer or not? There is no handler for them.
2.
Variables created in function scope, what happened with them?
Ex:
<?php
function test($a = 'abc') {
$c = 123;
return 1;
}
test();
echo 'end';
What happened with $a and $c when gc starts?
These variables still have refcount set to 1.
Will they still be removed? if yes then why and how (what is happening under the cover?)
3.
How can it help for cyclic references?
Ex
<?php
$a = array('abc');
$a[] =& $a;
unset($a);
where
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='abc',
1 => (refcount=1, is_ref=1)=...
)
1) The original object is replaced with the new one, and thus memory is freed instantly.
echo memory_get_usage().'<br/>';
$a = new stdClass();
echo memory_get_usage().'<br/>';
$a = new stdClass();
echo memory_get_usage().'<br/>';
2) They are gc'ed the second the function completes executing:
echo memory_get_usage().'<br/>';
function test($a = 'abc') {
$c = 123;
return 1;
}
echo memory_get_usage().'<br/>';
test();
echo memory_get_usage().'<br/>';
3) unsetting $a will leave the referenced variable in memory. You need to set the value to NULL first, and then unset.
echo memory_get_usage().'<br/>';
$a = array('abc');
echo memory_get_usage().'<br/>';
$a[] =& $a;
echo memory_get_usage().'<br/>';
$a = null;
unset($a);
echo memory_get_usage().'<br/>';

Does PHP take RAM when defining variable from variable and taking instance of object to another variable?

In case I have this code
<?php
$array = array();
for ($i=1;$i<100000;$i++){
$array[$i] = md5(rand(0,9999999999999999));
}
$array2 = $array;
$array takes about 0.5MB RAM, let's say. Does PHP proccess take about 1.0MB RAM with $array2 = $array; ? and in this case
<?php
class rand{
public $array;
function rand(){
$this->array = array();
for ($i=1;$i<100000;$i++){
$this->array[$i] = md5(rand(0,9999999999999999));
}
}
}
$class = new rand();
$class2 = $class;
$class takes about 0.5MB RAM, let's say
. Does PHP proccess take 1.0MB with $class2 = $class?
is it same?
Tests:
First
Second
This is what the PHP manual in the reference section warns about: the Engine is smart enough. Setting the $array2 = $array; does not cause duplicate storage, as PHP recognizes they are still both the same. However, try a $array[2] = 'something;' after that. PHP detects the difference, and only then will copy the values.
<?php
$array = array();
for ($i=1;$i<100000;$i++){
$array[$i] = md5(rand(0,9999999999999999));
}
echo memory_get_usage().PHP_EOL;
$array2 = $array;
echo memory_get_usage().PHP_EOL;
$array['foo'] = 'bar';
echo memory_get_usage().PHP_EOL;
//17252052
//17252156
//23776652
Classes are references by default, and only a clone $object would result in 2 objects in PHP >= 5.

How can I use var_dump + output buffering without memory errors?

I'm using a debugging aid in an application that uses var_dump() with output buffering to capture variables and display them. However, I'm running into an issue with large objects that end up using up too much memory in the buffer.
function getFormattedOutput(mixed $var) {
if (isTooLarge($var)) {
return 'Too large! Abort!'; // What a solution *might* look like
}
ob_start();
var_dump($var); // Fatal error: Allowed memory size of 536870912 bytes exhausted
$data = ob_get_clean();
// Return the nicely-formated data to use later
return $data
}
Is there a way I can prevent this? Or a work-around to detect that it's about to output a gigantic amount of info for a particular variable? I don't really have control which variables get passed into this function. It could be any type.
As all the others are mentioning what you ask is impossible. The only thing you can do is try to handle it as good as possible.
What you can try is to split it up into smaller pieces and then combine it. I've created a little test to try and get the memory error. Obviously a real world example might behave differently, but this seems to do the trick.
<?php
define('mem_limit', return_bytes(ini_get('memory_limit'))); //allowed memory
/*
SIMPLE TEST CLASS
*/
class test { }
$loop = 260;
$t = new Test();
for ($x=0;$x<=$loop;$x++) {
$v = 'test'.$x;
$t->$v = new Test();
for ($y=0;$y<=$loop;$y++) {
$v2 = 'test'.$y;
$t->$v->$v2 = str_repeat('something to test! ', 200);
}
}
/* ---------------- */
echo saferVarDumpObject($t);
function varDumpToString($v) {
ob_start();
var_dump($v);
$content = ob_get_contents();
ob_end_clean();
return $content;
}
function saferVarDumpObject($var) {
if (!is_object($var) && !is_array($var))
return varDumpToString($var);
$content = '';
foreach($var as $v) {
$content .= saferVarDumpObject($v);
}
//adding these smaller pieces to a single var works fine.
//returning the complete larger piece gives memory error
$length = strlen($content);
$left = mem_limit-memory_get_usage(true);
if ($left>$length)
return $content; //enough memory left
echo "WARNING! NOT ENOUGH MEMORY<hr>";
if ($left>100) {
return substr($content, 0, $left-100); //100 is a margin I choose, return everything you have that fits in the memory
} else {
return ""; //return nothing.
}
}
function return_bytes($val) {
$val = trim($val);
$last = strtolower($val[strlen($val)-1]);
switch($last) {
// The 'G' modifier is available since PHP 5.1.0
case 'g':
$val *= 1024;
case 'm':
$val *= 1024;
case 'k':
$val *= 1024;
}
return $val;
}
?>
UPDATE
The version above still has some error. I recreated it to use a class and some other functions
Check for recursion
Fix for single large attribute
Mimic var_dump output
trigger_error on warning to be able to catch/hide it
As shown in the comments, the resource identifier for a class is different from the output of var_dump. As far as I can tell the other things are equal.
<?php
/*
RECURSION TEST
*/
class sibling {
public $brother;
public $sister;
}
$brother = new sibling();
$sister = new sibling();
$brother->sister = $sister;
$sister->sister = $brother;
Dump::Safer($brother);
//simple class
class test { }
/*
LARGE TEST CLASS - Many items
*/
$loop = 260;
$t = new Test();
for ($x=0;$x<=$loop;$x++) {
$v = 'test'.$x;
$t->$v = new Test();
for ($y=0;$y<=$loop;$y++) {
$v2 = 'test'.$y;
$t->$v->$v2 = str_repeat('something to test! ', 200);
}
}
//Dump::Safer($t);
/* ---------------- */
/*
LARGE TEST CLASS - Large attribute
*/
$a = new Test();
$a->t2 = new Test();
$a->t2->testlargeattribute = str_repeat('1', 268435456 - memory_get_usage(true) - 1000000);
$a->smallattr1 = 'test small1';
$a->smallattr2 = 'test small2';
//Dump::Safer($a);
/* ---------------- */
class Dump
{
private static $recursionhash;
private static $memorylimit;
private static $spacing;
private static $mimicoutput = true;
final public static function MimicOutput($v) {
//show results similar to var_dump or without array/object information
//defaults to similar as var_dump and cancels this on out of memory warning
self::$mimicoutput = $v===false ? false : true;
}
final public static function Safer($var) {
//set defaults
self::$recursionhash = array();
self::$memorylimit = self::return_bytes(ini_get('memory_limit'));
self::$spacing = 0;
//echo output
echo self::saferVarDumpObject($var);
}
final private static function saferVarDumpObject($var) {
if (!is_object($var) && !is_array($var))
return self::Spacing().self::varDumpToString($var);
//recursion check
$hash = spl_object_hash($var);
if (!empty(self::$recursionhash[$hash])) {
return self::Spacing().'*RECURSION*'.self::Eol();
}
self::$recursionhash[$hash] = true;
//create a similar output as var dump to identify the instance
$content = self::Spacing() . self::Header($var);
//add some spacing to mimic vardump output
//Perhaps not the best idea because the idea is to use as little memory as possible.
self::$spacing++;
//Loop trough everything to output the result
foreach($var as $k=>$v) {
$content .= self::Spacing().self::Key($k).self::Eol().self::saferVarDumpObject($v);
}
self::$spacing--;
//decrease spacing and end the object/array
$content .= self::Spacing().self::Footer().self::Eol();
//adding these smaller pieces to a single var works fine.
//returning the complete larger piece gives memory error
//length of string and the remaining memory
$length = strlen($content);
$left = self::$memorylimit-memory_get_usage(true);
//enough memory left?
if ($left>$length)
return $content;
//show warning
trigger_error('Not enough memory to dump "'.get_class($var).'" memory left:'.$left, E_USER_WARNING);
//stop mimic output to prevent fatal memory error
self::MimicOutput(false);
if ($left>100) {
return substr($content, 0, $left-100); //100 is a margin I chose, return everything you have that fits in the memory
} else {
return ""; //return nothing.
}
}
final private static function Spacing() {
return self::$mimicoutput ? str_repeat(' ', self::$spacing*2) : '';
}
final private static function Eol() {
return self::$mimicoutput ? PHP_EOL : '';
}
final private static function Header($var) {
//the resource identifier for an object is WRONG! Its always 1 because you are passing around parts and not the actual object. Havent foundnd a fix yet
return self::$mimicoutput ? (is_array($var) ? 'array('.count($var).')' : 'object('.get_class($var).')#'.intval($var).' ('.count((array)$var).')') . ' {'.PHP_EOL : '';
}
final private static function Footer() {
return self::$mimicoutput ? '}' : '';
}
final private static function Key($k) {
return self::$mimicoutput ? '['.(gettype($k)=='string' ? '"'.$k.'"' : $k ).']=>' : '';
}
final private static function varDumpToString($v) {
ob_start();
var_dump($v);
$length = strlen($v);
$left = self::$memorylimit-memory_get_usage(true);
//enough memory left with some margin?
if ($left-100>$length) {
$content = ob_get_contents();
ob_end_clean();
return $content;
}
ob_end_clean();
//show warning
trigger_error('Not enough memory to dump "'.gettype($v).'" memory left:'.$left, E_USER_WARNING);
if ($left>100) {
$header = gettype($v).'('.strlen($v).')';
return $header . substr($v, $left - strlen($header));
} else {
return ""; //return nothing.
}
}
final private static function return_bytes($val) {
$val = trim($val);
$last = strtolower($val[strlen($val)-1]);
switch($last) {
// The 'G' modifier is available since PHP 5.1.0
case 'g':
$val *= 1024;
case 'm':
$val *= 1024;
case 'k':
$val *= 1024;
}
return $val;
}
}
?>
Well, if the physical memory is limited (you see the fatal error:)
Fatal error: Allowed memory size of 536870912 bytes exhausted
I would suggest to do the output buffering on disk (see callback parameter on ob_start). Output buffering works chunked, that means, if there still is enough memory to keep the single chunk in memory, you can store it into a temporary file.
// handle output buffering via callback, set chunksize to one kilobyte
ob_start($output_callback, $chunk_size = 1024);
However you must keep in mind that this will only prevent the fatal error while buffering. If you now want to return the buffer, you still need to have enough memory or you return the file-handle or file-path so that you can also stream the output.
However you can use that file then to obtain the size in bytes needed. The overhead for PHP strings is not much IIRC, so if there still is enough memory free for the filesize this should work well. You can substract offset to have a little room and play safe. Just try and error a little what it makes.
Some Example code (PHP 5.4):
<?php
/**
* #link http://stackoverflow.com/questions/5446647/how-can-i-use-var-dump-output-buffering-without-memory-errors/
*/
class OutputBuffer
{
/**
* #var int
*/
private $chunkSize;
/**
* #var bool
*/
private $started;
/**
* #var SplFileObject
*/
private $store;
/**
* #var bool Set Verbosity to true to output analysis data to stderr
*/
private $verbose = true;
public function __construct($chunkSize = 1024) {
$this->chunkSize = $chunkSize;
$this->store = new SplTempFileObject();
}
public function start() {
if ($this->started) {
throw new BadMethodCallException('Buffering already started, can not start again.');
}
$this->started = true;
$result = ob_start(array($this, 'bufferCallback'), $this->chunkSize);
$this->verbose && file_put_contents('php://stderr', sprintf("Starting Buffering: %d; Level %d\n", $result, ob_get_level()));
return $result;
}
public function flush() {
$this->started && ob_flush();
}
public function stop() {
if ($this->started) {
ob_flush();
$result = ob_end_flush();
$this->started = false;
$this->verbose && file_put_contents('php://stderr', sprintf("Buffering stopped: %d; Level %d\n", $result, ob_get_level()));
}
}
private function bufferCallback($chunk, $flags) {
$chunkSize = strlen($chunk);
if ($this->verbose) {
$level = ob_get_level();
$constants = ['PHP_OUTPUT_HANDLER_START', 'PHP_OUTPUT_HANDLER_WRITE', 'PHP_OUTPUT_HANDLER_FLUSH', 'PHP_OUTPUT_HANDLER_CLEAN', 'PHP_OUTPUT_HANDLER_FINAL'];
$flagsText = '';
foreach ($constants as $i => $constant) {
if ($flags & ($value = constant($constant)) || $value == $flags) {
$flagsText .= (strlen($flagsText) ? ' | ' : '') . $constant . "[$value]";
}
}
file_put_contents('php://stderr', "Buffer Callback: Chunk Size $chunkSize; Flags $flags ($flagsText); Level $level\n");
}
if ($flags & PHP_OUTPUT_HANDLER_FINAL) {
return TRUE;
}
if ($flags & PHP_OUTPUT_HANDLER_START) {
$this->store->fseek(0, SEEK_END);
}
$chunkSize && $this->store->fwrite($chunk);
if ($flags & PHP_OUTPUT_HANDLER_FLUSH) {
// there is nothing to d
}
if ($flags & PHP_OUTPUT_HANDLER_CLEAN) {
$this->store->ftruncate(0);
}
return "";
}
public function getSize() {
$this->store->fseek(0, SEEK_END);
return $this->store->ftell();
}
public function getBufferFile() {
return $this->store;
}
public function getBuffer() {
$array = iterator_to_array($this->store);
return implode('', $array);
}
public function __toString() {
return $this->getBuffer();
}
public function endClean() {
return ob_end_clean();
}
}
$buffer = new OutputBuffer();
echo "Starting Buffering now.\n=======================\n";
$buffer->start();
foreach (range(1, 10) as $iteration) {
$string = "fill{$iteration}";
echo str_repeat($string, 100), "\n";
}
$buffer->stop();
echo "Buffering Results:\n==================\n";
$size = $buffer->getSize();
echo "Buffer Size: $size (string length: ", strlen($buffer), ").\n";
echo "Peeking into buffer: ", var_dump(substr($buffer, 0, 10)), ' ...', var_dump(substr($buffer, -10)), "\n";
Output:
STDERR: Starting Buffering: 1; Level 1
STDERR: Buffer Callback: Chunk Size 1502; Flags 1 (PHP_OUTPUT_HANDLER_START[1]); Level 1
STDERR: Buffer Callback: Chunk Size 1503; Flags 0 (PHP_OUTPUT_HANDLER_WRITE[0]); Level 1
STDERR: Buffer Callback: Chunk Size 1503; Flags 0 (PHP_OUTPUT_HANDLER_WRITE[0]); Level 1
STDERR: Buffer Callback: Chunk Size 602; Flags 4 (PHP_OUTPUT_HANDLER_FLUSH[4]); Level 1
STDERR: Buffer Callback: Chunk Size 0; Flags 8 (PHP_OUTPUT_HANDLER_FINAL[8]); Level 1
STDERR: Buffering stopped: 1; Level 0
Starting Buffering now.
=======================
Buffering Results:
==================
Buffer Size: 5110 (string length: 5110).
Peeking into buffer: string(10) "fill1fill1"
...string(10) "l10fill10\n"
When you insall xdebug you can limit how deep var_dump follows objects. In some software products you might encounter a kind of recursion, which bloats the output of var_dump.
Other than that, you could raise the memory limit.
See http://www.xdebug.org/docs/display
I'm sorry, but I think there is no solution for your problem. You are asking for the determination of a size to prevent the memory allocation for that size. PHP can't give you an answer about "how much memory will it consume", as the ZVAL structs are created at the time of usage in PHP. Please refer to Programming PHP - 14.5. Memory Management for an overview of PHP's memory allocation internals.
You gave the correct hint "there can be anything in it" and this is the problem from my point of view. There is an architectural problem that leads to the case you describe. And I think you try to solve it on the wrong end.
For example: you can start with a switch for each type in php and try to set limits for each size. This lasts as long as nobody comes to the idea of changing the memory limit within the process.
Xdebug is a good solution as it keeps you application from exploding because of a (even non-business-critical) log function and it is a bad solution as you should no activate xdebug in production.
I think that a memory exception is the correct behavior and you should not try to work around it.
[rant]If the one who dumps a 50 megabytes or more string does not care about his/her app behavior, he/she deserves to suffer from it ;)[/rant]
I do not believe that there is any way to determine how much memory a specific function will eventually take up. One thing you can do is use memory_get_usage() to check how much memory the script is currently taking right before $largeVar is set, then compare it with the amount after. This will give you a good idea of the size of $largeVar, and you can run trials to determine what a maximum acceptable size limit would be before you exit gracefully.
You could also reimplement the var_dump() function yourself. Have the function walk through the structure and echo the resulting content as it is generated, or store it in a temp file, rather that storing a gigantic string in memory. This will allow you to get the same desired result, but without the memory problems you are encountering.

Why does this simple php script leak memory?

In hopes of trying to avoid future memory leaks in php programs (drupal modules, etc.) I've been messing around with simple php scripts that leak memory.
Could a php expert help me find what about this script causes the memory usage to continually climb?
Try running it yourself, changing various parameters. The results are interesting. Here it is:
<?php
function memstat() {
print "current memory usage: ". memory_get_usage() . "\n";
}
function waste_lots_of_memory($iters) {
$i = 0;
$object = new StdClass;
for (;$i < $iters; $i++) {
$object->{"member_" . $i} = array("blah blah blha" => 12345);
$object->{"membersonly_" . $i} = new StdClass;
$object->{"onlymember"} = array("blah blah blha" => 12345);
}
unset($object);
}
function waste_a_little_less_memory($iters) {
$i = 0;
$object = new StdClass;
for (;$i < $iters; $i++) {
$object->{"member_" . $i} = array("blah blah blha" => 12345);
$object->{"membersonly_" . $i} = new StdClass;
$object->{"onlymember"} = array("blah blah blha" => 12345);
unset($object->{"membersonly_". $i});
unset($object->{"member_" . $i});
unset($object->{"onlymember"});
}
unset($object);
}
memstat();
waste_a_little_less_memory(1000000);
memstat();
waste_lots_of_memory(10000);
memstat();
For me, the output is:
current memory usage: 73308
current memory usage: 74996
current memory usage: 506676
[edited to unset more object members]
unset() doesn't free the memory used by a variable. The memory is freed when the "garbage collector" (in quotes since PHP didn't have a real garbage collector before version 5.3.0, just a memory free routine which worked mostly on primitives) sees fit.
Also, technically, you shouldn't need to call unset() since the $object variable is limited to the scope of your function.
Here is a script to demonstrate the difference. I modified your memstat() function to show the memory difference since the last call.
<?php
function memdiff() {
static $int = null;
$current = memory_get_usage();
if ($int === null) {
$int = $current;
} else {
print ($current - $int) . "\n";
$int = $current;
}
}
function object_no_unset($iters) {
$i = 0;
$object = new StdClass;
for (;$i < $iters; $i++) {
$object->{"member_" . $i}= array("blah blah blha" => 12345);
$object->{"membersonly_" . $i}= new StdClass;
$object->{"onlymember"}= array("blah blah blha" => 12345);
}
}
function object_parent_unset($iters) {
$i = 0;
$object = new StdClass;
for (;$i < $iters; $i++) {
$object->{"member_" . $i}= array("blah blah blha" => 12345);
$object->{"membersonly_" . $i}= new StdClass;
$object->{"onlymember"}= array("blah blah blha" => 12345);
}
unset ($object);
}
function object_item_unset($iters) {
$i = 0;
$object = new StdClass;
for (;$i < $iters; $i++) {
$object->{"member_" . $i}= array("blah blah blha" => 12345);
$object->{"membersonly_" . $i}= new StdClass;
$object->{"onlymember"}= array("blah blah blha" => 12345);
unset ($object->{"membersonly_" . $i});
unset ($object->{"member_" . $i});
unset ($object->{"onlymember"});
}
unset ($object);
}
function array_no_unset($iters) {
$i = 0;
$object = array();
for (;$i < $iters; $i++) {
$object["member_" . $i] = array("blah blah blha" => 12345);
$object["membersonly_" . $i] = new StdClass;
$object["onlymember"] = array("blah blah blha" => 12345);
}
}
function array_parent_unset($iters) {
$i = 0;
$object = array();
for (;$i < $iters; $i++) {
$object["member_" . $i] = array("blah blah blha" => 12345);
$object["membersonly_" . $i] = new StdClass;
$object["onlymember"] = array("blah blah blha" => 12345);
}
unset ($object);
}
function array_item_unset($iters) {
$i = 0;
$object = array();
for (;$i < $iters; $i++) {
$object["member_" . $i] = array("blah blah blha" => 12345);
$object["membersonly_" . $i] = new StdClass;
$object["onlymember"] = array("blah blah blha" => 12345);
unset ($object["membersonly_" . $i]);
unset ($object["member_" . $i]);
unset ($object["onlymember"]);
}
unset ($object);
}
$iterations = 100000;
memdiff(); // Get initial memory usage
object_item_unset ($iterations);
memdiff();
object_parent_unset ($iterations);
memdiff();
object_no_unset ($iterations);
memdiff();
array_item_unset ($iterations);
memdiff();
array_parent_unset ($iterations);
memdiff();
array_no_unset ($iterations);
memdiff();
?>
If you are using objects, make sure the classes implements __unset() in order to allow unset() to properly clear resources. Try to avoid as much as possible the use of variable structure classes such as stdClass or assigning values to members which are not located in your class template as memory assigned to those are usually not cleared properly.
PHP 5.3.0 and up has a better garbage collector but it is disabled by default. To enable it, you must call gc_enable() once.
memory_get_usage() "Returns the amount of memory, in bytes, that's currently being allocated to your PHP script."
That's the amount of memory allocated to the process by the OS, not the amount of memory used by assigned variables. PHP does not always release memory back to the OS -- but that memory can still be re-used when new variables are allocated.
Demonstrating this is simple. Change the end of your script to:
memstat();
waste_lots_of_memory(10000);
memstat();
waste_lots_of_memory(10000);
memstat();
Now, if you're correct, and PHP is actually leaking memory, you should see memory useage grow twice. However, here's the actual result:
current memory usage: 88272
current memory usage: 955792
current memory usage: 955808
This is because memory "freed" after the initial invocation of waste_lots_of_memory() is re-used by the second invocation.
In my 5 years with PHP, I've written scripts that have processed millions of objects and gigabytes of data over a period of hours, and scripts that have run for months at a time. PHP's memory management isn't great, but it's not nearly as bad as you're making it out to be.
memory_get_usage reports how much memory php has allocated from the os. It doesn't necessarily match the size of all variables in use. If php has a peak use of memory, it may decide not to return the unused amount of memory right away. In your example, the function waste_a_little_less_memory unsets unused variables over time. So the peak usage is relatively small. The waste_lots_of_memory builds up a lot of variables (=lots of used memory) before deallocating it. So the peak usage is much larger.
My understanding of memory_get_usage() is that it's output can depend on a wide range of operating system and version factors.
More importantly, unsetting a variable does not instantly free it's memory, deallocate it from the process, and give it back to the operating system (again, characteristics of this operation are operating system dependent).
In short, you probably need a more complicated setup to look at memory leaks.
I'm not sure about the exact workings of it in PHP, but in some other languages an object containing other objects, when set to null, does not inherently set the other objects to null. It terminates the reference to those objects, but as PHP does not have "garbage collection" in a Java sense, the sub-objects exist in memory until they are removed individually.
memory_get_usage() does not returns the immediate memory usage, but stored memory to run the process.
IN the case of a huge array unset($array_a) will not release memory but consume more according to the memory_get_usage() in my system...
$array_a="(SOME big array)";
$array_b="";
//copy array_a to array_b
for($line=0; $line<100;$line++){
$array_b[$line]=$array_a[$line];
}
unset($array_a); //having this shows actually a bigger consume
print_r($array_b);
echo memory_get_usage();

Categories