measuring the elapsed time between code segments in PHP - php

From time time to time, I'd like to be able to measure the elapsed time between two segments of code. This is solely to be able to detect the bottlenecks within the code and improve what can be improved.
I'd like to design a function like that where the function should work with a global variable which echoes out the elapsed time between the current call and the last time it was called.
This way, you can use it many times one after the other.
And the function should be able to be calculate the differences in fractions of seconds such as 0.1 sec or 0.3 sec etc.
An example would probably explain it much better.
echo time_elapsed();
// This echo outputs nothing cause this is the starting case.
// There is nothing to compare against.
//
// 1st code section here
//
echo time_elapsed();
// This echo outputs 0.5 seconds.
// ...which means there has been 0.5 seconds passed
// ...since the last time time_elapsed() was fired
//
// 2nd code section here
//
echo time_elapsed()
// This echo outputs 0.2 seconds
//
// 3rd code section here
//
echo time_elapsed()
// This echo outputs 0.1 seconds etc
My question is what PHP utilities ( built-in functions ) do I need to use to achieve this kind of output?

A debugger like XDebug/Zend Debugger can give you this type of insight (plus much more), but here is a hint at how you can write a function like that:
function time_elapsed()
{
static $last = null;
$now = microtime(true);
if ($last != null) {
echo '<!-- ' . ($now - $last) . ' -->';
}
$last = $now;
}
Mainly the function microtime() is all you need in order to do the time calculations. To avoid a global variable, I use a static variable within the elapsed function. Alternatively, you could create a simple class that can encapsulate the required variables and make calls to a class method to track and output the time values.

From the first example in the php docs:
<?php
/**
* Simple function to replicate PHP 5 behaviour
*/
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
$time_start = microtime_float();
// Sleep for a while
usleep(100);
$time_end = microtime_float();
$time = $time_end - $time_start;
echo "Did nothing in $time seconds\n";

Something along these lines should work:
$start = microtime(true);
// Do something
sleep(2);
$end = (microtime(true) - $start);
echo "elapsed time: $end";

Same drew010 function (thanks!), only added custom comment and time displays in microseconds (us):
function time_elapsed($comment)
{
static $time_elapsed_last = null;
static $time_elapsed_start = null;
// $unit="s"; $scale=1000000; // output in seconds
// $unit="ms"; $scale=1000; // output in milliseconds
$unit="μs"; $scale=1; // output in microseconds
$now = microtime(true);
if ($time_elapsed_last != null) {
echo "\n";
echo '<!-- ';
echo "$comment: Time elapsed: ";
echo round(($now - $time_elapsed_last)*1000000)/$scale;
echo " $unit, total time: ";
echo round(($now - $time_elapsed_start)*1000000)/$scale;
echo " $unit -->";
echo "\n";
} else {
$time_elapsed_start=$now;
}
$time_elapsed_last = $now;
}
Example:
// Start timer
time_elapsed('');
// Do something
usleep(100);
time_elapsed('Now awake, sleep again');
// Do something
usleep(100);
time_elapsed('Game over');
Ouput:
<!-- Now awake, sleep again: Time elapsed: 100 us, total time: 100 us -->
<!-- Game over: Time elapsed: 100 us, total time: 200 us -->

Other factors affect the timing of your scripts. Example:
Complex code and recursive functions.
The type of web server being used, example: shared VS dedicated hosting.

<?php
$time_start = microtime(true);
// Sleep for a while (or your code which you want to measure)
usleep(100);
$time_end = microtime(true);
$time = $time_end - $time_start;
echo "Did nothing in $time seconds\n";
Source: http://php.net/manual/en/function.microtime.php#example-2568

My profiling needs in development are covered by this small yet powerful class:
<?php
class perflog {
protected $perflog = [];
public function start($tag) {
if (!isset($this->perflog[$tag])) $this->perflog[$tag] = 0;
$this->perflog[$tag] -= microtime(TRUE);
}
public function stop($tag) {
$this->perflog[$tag] += microtime(TRUE);
}
public function results() {
return $this->perflog;
}
}
See it in action here.
It is intended to be invoked via subsequent start(<tag>) and stop(<tag>) calls. It produces an array with the totalized times your code spent in the sections enclosed by the calls of start() and stop() with matching tags.
start-stop sequences may be nested and may be entered multiple times, thus summarizing the time spent in the enclosed section.
Its compactness ensures minimum performance impact. Dynamic tag creation can be used to let the program modify what it monitors. Typically this is extended with outputting or storing functions.

Related

PHP While loop not breaking, but works in debugger?

Have a simple script that loops through.
Works fine in debugger mode when i step through code. But when its ran on without debugger it never ends / breaks.
Should run for as long as the time i set then break out of the loop. As i said this works perfectly fine in debugger, but when its ran without it, it just forever loops regardless of the time
Any suggestions why?
$time_start = microtime(true);
$n = 0;
while ( 1 ){
$n++ ;
echo $n;
$time_end = microtime(true);
$time = $time_end - $time_start;
if($time > 1.5){
break;
}
}
I want to use While, as the program will be used to listen to a socket.
but need to ensure the while ends if a time is met
It works, but PHP is very fast. So you have to be more patient, until the time is passed. Put your threshold down.
Maybe you should consider using hrtime instead.
When you are using the debugger, time is still running, so the delay until your next step is reached, is quickly over and meets the break condition.
$time_start = microtime(true);
$n = 0;
while ( 1 ){
$n++ ;
echo $n;
$time_end = microtime(true);
$time = $time_end - $time_start;
echo " :: $time\n";
if($time > 1.5){
break;
}
}
See the last results after slightly modifying your output.
1354524 :: 1.4999921321869
1354525 :: 1.4999930858612
1354526 :: 1.4999940395355
1354527 :: 1.4999949932098
1354528 :: 1.4999949932098
1354529 :: 1.4999961853027
1354530 :: 1.4999971389771
1354531 :: 1.4999980926514
1354532 :: 1.4999990463257
1354533 :: 1.4999990463257
1354534 :: 1.5
1354535 :: 1.5000011920929

PHP Infinite Loop Not Terminated after 60 seconds

I created an infinite PHP while loop which incremented a variable then echoed:
$num = 1;
while($num >0) {
echo $num . "<br/>";
$num++;
}
I was expecting this to be killed/terminated after 60 seconds as the setting in php.ini are as follows:
max_execution_time 60 60
max_input_time 60
Sorry if I'm wrong but I expected to see the job killed in the browser (no new echos!)...
Can anyone give me more information on infinite PHP jobs running and when they are actually killed on the server?
You are confusing execution time with wall clock time. They are not the same thing. The processor is using very little execution time on each loop of your code. It will eventually time you out but it's going to take a lot longer than a minute.
Think of it this way, your processor may be running at 2GHz. How many instructions do you think it takes to do one of your loops? The time on echo is big (i.e. slow) and it doesn't count toward processor execution time.
//using set_time_limit
// starting php code here
echo "starting...\n";
// set_time_limit(10); //either would work
ini_set("max_execution_time", 10); //either would work
function doSomeExpensiveWork($currentTime){
for ($r = 0; $r < 100000; $r++){
$x = tan(M_LNPI+log(ceil( date("s")*M_PI*M_LNPI+100)));
}
}
try{
while(true)
{
$currentTime = date("H:m:s");
echo $currentTime, "\n";
doSomeExpensiveWork($currentTime);
}
} catch (Exception $e) {
//echo 'Caught exception: ', $e->getMessage(), "\n";
}
echo "this will not be executed! $x";
// code end

Converting indentation with preg_replace (no callback)

I have some XML chunk returned by DOMDocument::saveXML(). It's already pretty indented, with two spaces per level, like so:
<?xml version="1.0"?>
<root>
<error>
<a>eee</a>
<b>sd</b>
</error>
</root>
As it's not possible to configure DOMDocument (AFAIK) about the indentation character(s), I thought it's possible to run a regular expression and change the indentation by replacing all two-space-pairs into a tab. This can be done with a callback function (Demo):
$xml_string = $doc->saveXML();
function callback($m)
{
$spaces = strlen($m[0]);
$tabs = $spaces / 2;
return str_repeat("\t", $tabs);
}
$xml_string = preg_replace_callback('/^(?:[ ]{2})+/um', 'callback', $xml_string);
I'm now wondering if it's possible to do this w/o a callback function (and without the e-modifier (EVAL)). Any regex wizards with an idea?
You can use \G:
preg_replace('/^ |\G /m', "\t", $string);
Did some benchmarks and got following results on Win32 with PHP 5.2 and 5.4:
>php -v
PHP 5.2.17 (cli) (built: Jan 6 2011 17:28:41)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2010 Zend Technologies
>php -n test.php
XML length: 21100
Iterations: 1000
callback: 2.3627231121063
\G: 1.4221360683441
while: 3.0971200466156
/e: 7.8781840801239
>php -v
PHP 5.4.0 (cli) (built: Feb 29 2012 19:06:50)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2012 Zend Technologies
>php -n test.php
XML length: 21100
Iterations: 1000
callback: 1.3771259784698
\G: 1.4414191246033
while: 2.7389969825745
/e: 5.5516891479492
Surprising that callback is faster than than \G in PHP 5.4 (altho that seems to depend on the data, \G is faster in some other cases).
For \G /^ |\G /m is used, and is a bit faster than /(?:^|\G) /m.
/(?>^|\G) /m is even slower than /(?:^|\G) /m.
/u, /S, /X switches didn't affect \G performance noticeably.
The while replace is fastest if depth is low (up to about 4 indentations, 8 spaces, in my test), but then gets slower as the depth increases.
The following code was used:
<?php
$base_iter = 1000;
$xml_string = str_repeat(<<<_STR_
<?xml version="1.0"?>
<root>
<error>
<a> eee </a>
<b> sd </b>
<c>
deep
deeper still
deepest !
</c>
</error>
</root>
_STR_
, 100);
//*** while ***
$re = '%# Match leading spaces following leading tabs.
^ # Anchor to start of line.
(\t*) # $1: Preserve any/all leading tabs.
[ ]{2} # Match "n" spaces.
%mx';
function conv_indent_while($xml_string) {
global $re;
while(preg_match($re, $xml_string))
$xml_string = preg_replace($re, "$1\t", $xml_string);
return $xml_string;
}
//*** \G ****
function conv_indent_g($string){
return preg_replace('/^ |\G /m', "\t", $string);
}
//*** callback ***
function callback($m)
{
$spaces = strlen($m[0]);
$tabs = $spaces / 2;
return str_repeat("\t", $tabs);
}
function conv_indent_callback($str){
return preg_replace_callback('/^(?:[ ]{2})+/m', 'callback', $str);
}
//*** callback /e ***
function conv_indent_e($str){
return preg_replace('/^(?: )+/me', 'str_repeat("\t", strlen("$0")/2)', $str);
}
//*** tests
function test2() {
global $base_iter;
global $xml_string;
$t = microtime(true);
for($i = 0; $i < $base_iter; ++$i){
$s = conv_indent_while($xml_string);
if(strlen($s) >= strlen($xml_string))
exit("strlen invalid 2");
}
return (microtime(true) - $t);
}
function test1() {
global $base_iter;
global $xml_string;
$t = microtime(true);
for($i = 0; $i < $base_iter; ++$i){
$s = conv_indent_g($xml_string);
if(strlen($s) >= strlen($xml_string))
exit("strlen invalid 1");
}
return (microtime(true) - $t);
}
function test0(){
global $base_iter;
global $xml_string;
$t = microtime(true);
for($i = 0; $i < $base_iter; ++$i){
$s = conv_indent_callback($xml_string);
if(strlen($s) >= strlen($xml_string))
exit("strlen invalid 0");
}
return (microtime(true) - $t);
}
function test3(){
global $base_iter;
global $xml_string;
$t = microtime(true);
for($i = 0; $i < $base_iter; ++$i){
$s = conv_indent_e($xml_string);
if(strlen($s) >= strlen($xml_string))
exit("strlen invalid 02");
}
return (microtime(true) - $t);
}
echo 'XML length: ' . strlen($xml_string) . "\n";
echo 'Iterations: ' . $base_iter . "\n";
echo 'callback: ' . test0() . "\n";
echo '\G: ' . test1() . "\n";
echo 'while: ' . test2() . "\n";
echo '/e: ' . test3() . "\n";
?>
The following simplistic solution first comes to mind:
$xml_string = str_replace(' ', "\t", $xml_string);
But I assume, you would like to limit the replacement to leading whitespace only. For that case, your current solution looks pretty clean to me. That said, you can do it without a callback or the e modifier, but you need to run it recursively to get the job done like so:
$re = '%# Match leading spaces following leading tabs.
^ # Anchor to start of line.
(\t*) # $1: Preserve any/all leading tabs.
[ ]{2} # Match "n" spaces.
%umx';
while(preg_match($re, $xml_string))
$xml_string = preg_replace($re, "$1\t", $xml_string);
Surprisingly, my testing shows this to be nearly twice as fast as the callback method. (I would have guessed the opposite.)
Note that Qtax has an elegant solution that works just fine (I gave it my +1). However, my benchmarks show it to be slower than the original callback method. I think this is because the expression /(?:^|\G) /um does not allow the regex engine to take advantage of the: "anchor at the beginning of the pattern" internal optimization. The RE engine is forced to test the pattern against each and every position in the target string. With pattern expressions beginning with the ^ anchor, the RE engine only needs to check at the beginning of each line which allows it to match much faster.
Excellent question! +1
Addendum/Correction:
I must apologize because the performance statements I made above are wrong. I ran the regexes against only one (non-representative) test file which had mostly tabs in the leading whitespace. When tested against a more realistic file having lots of leading spaces, my recursive method above performs significantly slower than the other two methods.
If anyone is interested, here is the benchmark script I used to measure the performance of each regex:
test.php
<?php // test.php 20120308_1200
require_once('inc/benchmark.inc.php');
// -------------------------------------------------------
// Test 1: Recursive method. (ridgerunner)
function tabify_leading_spaces_1($xml_string) {
$re = '%# Match leading spaces following leading tabs.
^ # Anchor to start of line.
(\t*) # $1: Any/all leading tabs.
[ ]{2} # Match "n" spaces.
%umx';
while(preg_match($re, $xml_string))
$xml_string = preg_replace($re, "$1\t", $xml_string);
return $xml_string;
}
// -------------------------------------------------------
// Test 2: Original callback method. (hakre)
function tabify_leading_spaces_2($xml_string) {
return preg_replace_callback('/^(?:[ ]{2})+/um', '_callback', $xml_string);
}
function _callback($m) {
$spaces = strlen($m[0]);
$tabs = $spaces / 2;
return str_repeat("\t", $tabs);
}
// -------------------------------------------------------
// Test 3: Qtax's elegantly simple \G method. (Qtax)
function tabify_leading_spaces_3($xml_string) {
return preg_replace('/(?:^|\G) /um', "\t", $xml_string);
}
// -------------------------------------------------------
// Verify we get the same results from all methods.
$data = file_get_contents('testdata.txt');
$data1 = tabify_leading_spaces_1($data);
$data2 = tabify_leading_spaces_2($data);
$data3 = tabify_leading_spaces_3($data);
if ($data1 == $data2 && $data2 == $data3) {
echo ("GOOD: Same results.\n");
} else {
exit("BAD: Different results.\n");
}
// Measure and print the function execution times.
$time1 = benchmark_12('tabify_leading_spaces_1', $data, 2, true);
$time2 = benchmark_12('tabify_leading_spaces_2', $data, 2, true);
$time3 = benchmark_12('tabify_leading_spaces_3', $data, 2, true);
?>
The above script uses the following handy little benchmarking function I wrote some time ago:
benchmark.inc.php
<?php // benchmark.inc.php
/*----------------------------------------------------------------------------
function benchmark_12($funcname, $p1, $reptime = 1.0, $verbose = true, $p2 = NULL) {}
By: Jeff Roberson
Created: 2010-03-17
Last edited: 2012-03-08
Discussion:
This function measures the time required to execute a given function by
calling it as many times as possible within an allowed period == $reptime.
A first pass determines a rough measurement of function execution time
by increasing the $nreps count by a factor of 10 - (i.e. 1, 10, 100, ...),
until an $nreps value is found which takes more than 0.01 secs to finish.
A second pass uses the value determined in the first pass to compute the
number of reps that can be performed within the allotted $reptime seconds.
The second pass then measures the time required to call the function the
computed number of times (which should take about $reptime seconds). The
average function execution time is then computed by dividing the total
measured elapsed time by the number of reps performed in that time, and
then all the pertinent values are returned to the caller in an array.
Note that this function is limited to measuring only those functions
having either one or two arguments that are passed by value and
not by reference. This is why the name of this function ends with "12".
Variations of this function can be easily cloned which can have more
than two parameters.
Parameters:
$funcname: String containing name of function to be measured. The
function to be measured must take one or two parameters.
$p1: First argument to be passed to $funcname function.
$reptime Target number of seconds allowed for benchmark test.
(float) (Default=1.0)
$verbose Boolean value determines if results are printed.
(bool) (Default=true)
$p2: Second (optional) argument to be passed to $funcname function.
Return value:
$result[] Array containing measured and computed values:
$result['funcname'] : $funcname - Name of function measured.
$result['msg'] : $msg - String with formatted results.
$result['nreps'] : $nreps - Number of function calls made.
$result['time_total'] : $time - Seconds to call function $nreps times.
$result['time_func'] : $t_func - Seconds to call function once.
$result['result'] : $result - Last value returned by function.
Variables:
$time: Float epoch time (secs since 1/1/1970) or benchmark elapsed secs.
$i: Integer loop counter.
$nreps Number of times function called in benchmark measurement loops.
----------------------------------------------------------------------------*/
function benchmark_12($funcname, $p1, $reptime = 1.0, $verbose = false, $p2 = NULL) {
if (!function_exists($funcname)) {
exit("\n[benchmark1] Error: function \"{$funcname}()\" does not exist.\n");
}
if (!isset($p2)) { // Case 1: function takes one parameter ($p1).
// Pass 1: Measure order of magnitude number of calls needed to exceed 10 milliseconds.
for ($time = 0.0, $n = 1; $time < 0.01; $n *= 10) { // Exponentially increase $nreps.
$time = microtime(true); // Mark start time. (sec since 1970).
for ($i = 0; $i < $n; ++$i) { // Loop $n times. ($n = 1, 10, 100...)
$result = ($funcname($p1)); // Call the function over and over...
}
$time = microtime(true) - $time; // Mark stop time. Compute elapsed secs.
$nreps = $n; // Number of reps just measured.
}
$t_func = $time / $nreps; // Function execution time in sec (rough).
// Pass 2: Measure time required to perform $nreps function calls (in about $reptime sec).
if ($t_func < $reptime) { // If pass 1 time was not pathetically slow...
$nreps = (int)($reptime / $t_func); // Figure $nreps calls to add up to $reptime.
$time = microtime(true); // Mark start time. (sec since 1970).
for ($i = 0; $i < $nreps; ++$i) { // Loop $nreps times (should take $reptime).
$result = ($funcname($p1)); // Call the function over and over...
}
$time = microtime(true) - $time; // Mark stop time. Compute elapsed secs.
$t_func = $time / $nreps; // Average function execution time in sec.
}
} else { // Case 2: function takes two parameters ($p1 and $p2).
// Pass 1: Measure order of magnitude number of calls needed to exceed 10 milliseconds.
for ($time = 0.0, $n = 1; $time < 0.01; $n *= 10) { // Exponentially increase $nreps.
$time = microtime(true); // Mark start time. (sec since 1970).
for ($i = 0; $i < $n; ++$i) { // Loop $n times. ($n = 1, 10, 100...)
$result = ($funcname($p1, $p2)); // Call the function over and over...
}
$time = microtime(true) - $time; // Mark stop time. Compute elapsed secs.
$nreps = $n; // Number of reps just measured.
}
$t_func = $time / $nreps; // Function execution time in sec (rough).
// Pass 2: Measure time required to perform $nreps function calls (in about $reptime sec).
if ($t_func < $reptime) { // If pass 1 time was not pathetically slow...
$nreps = (int)($reptime / $t_func); // Figure $nreps calls to add up to $reptime.
$time = microtime(true); // Mark start time. (sec since 1970).
for ($i = 0; $i < $nreps; ++$i) { // Loop $nreps times (should take $reptime).
$result = ($funcname($p1, $p2)); // Call the function over and over...
}
$time = microtime(true) - $time; // Mark stop time. Compute elapsed secs.
$t_func = $time / $nreps; // Average function execution time in sec.
}
}
$msg = sprintf("%s() Nreps:%7d Time:%7.3f s Function time: %.6f sec\n",
$funcname, $nreps, $time, $t_func);
if ($verbose) echo($msg);
return array('funcname' => $funcname, 'msg' => $msg, 'nreps' => $nreps,
'time_total' => $time, 'time_func' => $t_func, 'result' => $result);
}
?>
When I run test.php using the contents of benchmark.inc.php, here's the results I get:
GOOD: Same results.
tabify_leading_spaces_1() Nreps: 1756 Time: 2.041 s Function time: 0.001162 sec
tabify_leading_spaces_2() Nreps: 1738 Time: 1.886 s Function time: 0.001085 sec
tabify_leading_spaces_3() Nreps: 2161 Time: 2.044 s Function time: 0.000946 sec
Bottom line: I would recommend using Qtax's method.
Thanks Qtax!

PHP Performance : Copy vs. Reference

Hey there. Today I wrote a small benchmark script to compare performance of copying variables vs. creating references to them. I was expecting, that creating references to large arrays for example would be significantly slower than copying the whole array. Here is my benchmark code:
<?php
$array = array();
for($i=0; $i<100000; $i++) {
$array[] = mt_rand();
}
function recursiveCopy($array, $count) {
if($count === 1000)
return;
$foo = $array;
recursiveCopy($array, $count+1);
}
function recursiveReference($array, $count) {
if($count === 1000)
return;
$foo = &$array;
recursiveReference($array, $count+1);
}
$time = microtime(1);
recursiveCopy($array, 0);
$copyTime = (microtime(1) - $time);
echo "Took " . $copyTime . "s \n";
$time = microtime(1);
recursiveReference($array, 0);
$referenceTime = (microtime(1) - $time);
echo "Took " . $referenceTime . "s \n";
echo "Reference / Copy: " . ($referenceTime / $copyTime);
The actual result I got was, that recursiveReference took about 20 times (!) as long as recursiveCopy.
Can somebody explain this PHP behaviour?
PHP will very likely implement copy-on-write for its arrays, meaning when you "copy" an array, PHP doesn't do all the work of physically copying the memory until you modify one of the copies and your variables can no longer reference the same internal representation.
Your benchmarking is therefore fundamentally flawed, as your recursiveCopy function doesn't actually copy the object; if it did, you would run out of memory very quickly.
Try this: By assigning to an element of the array you force PHP to actually make a copy. You'll find you run out of memory pretty quickly as none of the copies go out of scope (and aren't garbage collected) until the recursive function reaches its maximum depth.
function recursiveCopy($array, $count) {
if($count === 1000)
return;
$foo = $array;
$foo[9492] = 3; // Force PHP to copy the array
recursiveCopy($array, $count+1);
}
in recursiveReference you're calling recursiveCopy... this doesn't make any sense, in this case you're calling recursiveReference just once. correct your code, rund the benchmark again and come back with your new results.
in addition, i don't think it's useful for a benchmark to do this recursively. a better solution would be to call a function 1000 times in a loop - once with the array directly and one with a reference to that array.
You don't need to (and thus shouldn't) assign or pass variables by reference just for performance reasons. PHP does such optimizations automatically.
The test you ran is flawed because of these automatic optimizations. In ran the following test instead:
<?php
for($i=0; $i<100000; $i++) {
$array[] = mt_rand();
}
$time = microtime(1);
for($i=0; $i<1000; $i++) {
$copy = $array;
unset($copy);
}
$duration = microtime(1) - $time;
echo "Normal Assignment and don't write: $duration<br />\n";
$time = microtime(1);
for($i=0; $i<1000; $i++) {
$copy =& $array;
unset($copy);
}
$duration = microtime(1) - $time;
echo "Assignment by Reference and don't write: $duration<br />\n";
$time = microtime(1);
for($i=0; $i<1000; $i++) {
$copy = $array;
$copy[0] = 0;
unset($copy);
}
$duration = microtime(1) - $time;
echo "Normal Assignment and write: $duration<br />\n";
$time = microtime(1);
for($i=0; $i<1000; $i++) {
$copy =& $array;
$copy[0] = 0;
unset($copy);
}
$duration = microtime(1) - $time;
echo "Assignment by Reference and write: $duration<br />\n";
?>
This was the output:
//Normal Assignment without write: 0.00023698806762695
//Assignment by Reference without write: 0.00023508071899414
//Normal Assignment with write: 21.302103042603
//Assignment by Reference with write: 0.00030708312988281
As you can see there is no significant performance difference in assigning by reference until you actually write to the copy, i.e. when there is also a functional difference.
Generally speaking in PHP, calling by reference is not something you'd do for performance reasons; it's something you'd do for functional reasons - ie because you actually want the referenced variable to be updated.
If you don't have a functional reason for calling by reference then you should stick with regular parameter passing, because PHP handles things perfectly efficiently that way.
(that said, as others have pointed out, your example code isn't exactly doing what you think it is anyway ;))
In recursiveReference() function you call recursiveCopy() function. It it what you really intended to do?
You do nothing with $foo variable - probably it was supposed to be used in further method call?
Passing variable by reference should generally save stack memory in case of passing large objects.
recursiveReference is calling recursiveCopy.
Not that that would necessarily harm performance, but that's probably not what you're trying to do.
Not sure why performance is slower, but it doesn't reflect the measurement you're trying to make.

What's better at freeing memory with PHP: unset() or $var = null

I realise the second one avoids the overhead of a function call (update, is actually a language construct), but it would be interesting to know if one is better than the other. I have been using unset() for most of my coding, but I've recently looked through a few respectable classes found off the net that use $var = null instead.
Is there a preferred one, and what is the reasoning?
It was mentioned in the unset manual's page in 2009:
unset() does just what its 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.
(Since 2013, that unset man page don't include that section anymore)
Note that until php5.3, if you have two objects in circular reference, such as in a parent-child relationship, calling unset() on the parent object will not free the memory used for the parent reference in the child object. (Nor will the memory be freed when the parent object is garbage-collected.) (bug 33595)
The question "difference between unset and = null" details some differences:
unset($a) also removes $a from the symbol table; for example:
$a = str_repeat('hello world ', 100);
unset($a);
var_dump($a);
Outputs:
Notice: Undefined variable: a in xxx
NULL
But when $a = null is used:
$a = str_repeat('hello world ', 100);
$a = null;
var_dump($a);
Outputs:
NULL
It seems that $a = null is a bit faster than its unset() counterpart: updating a symbol table entry appears to be faster than removing it.
when you try to use a non-existent (unset) variable, an error will be triggered and the value for the variable expression will be null. (Because, what else should PHP do? Every expression needs to result in some value.)
A variable with null assigned to it is still a perfectly normal variable though.
unset is not actually a function, but a language construct. It is no more a function call than a return or an include.
Aside from performance issues, using unset makes your code's intent much clearer.
By doing an unset() on a variable, you've essentially marked the variable for 'garbage collection' (PHP doesn't really have one, but for example's sake) so the memory isn't immediately available. The variable no longer houses the data, but the stack remains at the larger size. Doing the null method drops the data and shrinks the stack memory almost immediately.
This has been from personal experience and others as well. See the comments of the unset() function here.
I personally use unset() between iterations in a loop so that I don't have to have the delay of the stack being yo-yo'd in size. The data is gone, but the footprint remains. On the next iteration, the memory is already being taken by php and thus, quicker to initialize the next variable.
<?php
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$a = 'a';
$a = NULL;
}
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds\r\n";
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$a = 'a';
unset($a);
}
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds\r\n";
?>
Per that it seems like "= null" is faster.
PHP 5.4 results:
took 0.88389301300049 seconds
took 2.1757180690765 seconds
PHP 5.3 results:
took 1.7235369682312 seconds
took 2.9490959644318 seconds
PHP 5.2 results:
took 3.0069220066071 seconds
took 4.7002630233765 seconds
PHP 5.1 results:
took 2.6272349357605 seconds
took 5.0403649806976 seconds
Things start to look different with PHP 5.0 and 4.4.
5.0:
took 10.038941144943 seconds
took 7.0874409675598 seconds
4.4:
took 7.5352551937103 seconds
took 6.6245851516724 seconds
Keep in mind microtime(true) doesn't work in PHP 4.4 so I had to use the microtime_float example given in php.net/microtime / Example #1.
It works in a different way for variables copied by reference:
$a = 5;
$b = &$a;
unset($b); // just say $b should not point to any variable
print $a; // 5
$a = 5;
$b = &$a;
$b = null; // rewrites value of $b (and $a)
print $a; // nothing, because $a = null
It makes a difference with array elements.
Consider this example
$a = array('test' => 1);
$a['test'] = NULL;
echo "Key test ", array_key_exists('test', $a)? "exists": "does not exist";
Here, the key 'test' still exists. However, in this example
$a = array('test' => 1);
unset($a['test']);
echo "Key test ", array_key_exists('test', $a)? "exists": "does not exist";
the key no longer exists.
Regarding objects, especially in lazy-load scenario, one should consider garbage collector is running in idle CPU cycles, so presuming you're going into trouble when a lot of objects are loading small time penalty will solve the memory freeing.
Use time_nanosleep to enable GC to collect memory.
Setting variable to null is desirable.
Tested on production server, originally the job consumed 50MB and then was halted.
After nanosleep was used 14MB was constant memory consumption.
One should say this depends on GC behaviour which may change from PHP version to version.
But it works on PHP 5.3 fine.
eg. this sample (code taken form VirtueMart2 google feed)
for($n=0; $n<count($ids); $n++)
{
//unset($product); //usefull for arrays
$product = null
if( $n % 50 == 0 )
{
// let GC do the memory job
//echo "<mem>" . memory_get_usage() . "</mem>";//$ids[$n];
time_nanosleep(0, 10000000);
}
$product = $productModel->getProductSingle((int)$ids[$n],true, true, true);
...
For the record, and excluding the time that it takes:
<?php
echo "<hr>First:<br>";
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";
echo memory_get_peak_usage() . "<br>\n";
echo "<hr>Unset:<br>";
unset($x);
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";
echo memory_get_peak_usage() . "<br>\n";
echo "<hr>Null:<br>";
$x=null;
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";
echo memory_get_peak_usage() . "<br>\n";
echo "<hr>function:<br>";
function test() {
$x = str_repeat('x', 80000);
}
echo memory_get_usage() . "<br>\n";
echo memory_get_peak_usage() . "<br>\n";
echo "<hr>Reasign:<br>";
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";
echo memory_get_peak_usage() . "<br>\n";
It returns
First:
438296
438352
Unset:
438296
438352
Null:
438296
438352
function:
438296
438352
Reasign:
438296
520216 <-- double usage.
Conclusion, both null and unset free memory as expected (not only at the end of the execution). Also, reassigning a variable holds the value twice at some point (520216 versus 438352)
PHP 7 is already worked on such memory management issues and its reduced up-to minimal usage.
<?php
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$a = 'a';
$a = NULL;
}
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds\r\n";
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$a = 'a';
unset($a);
}
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds\r\n";
?>
PHP 7.1 Outpu:
took 0.16778993606567 seconds
took 0.16630101203918 seconds
Code example from comment
echo "PHP Version: " . phpversion() . PHP_EOL . PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$a = 'a';
$a = NULL;
}
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds" . PHP_EOL;
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$a = 'a';
unset($a);
}
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds" . PHP_EOL;
Running in docker container from image php:7.4-fpm and others..
PHP Version: 7.4.8
took 0.22569918632507 seconds null
took 0.11705803871155 seconds unset
took 0.20791196823121 seconds null
took 0.11697316169739 seconds unset
PHP Version: 7.3.20
took 0.22086310386658 seconds null
took 0.11882591247559 seconds unset
took 0.21383500099182 seconds null
took 0.11916995048523 seconds unset
PHP Version: 7.2.32
took 0.24728178977966 seconds null
took 0.12719893455505 seconds unset
took 0.23839902877808 seconds null
took 0.12744522094727 seconds unset
PHP Version: 7.1.33
took 0.51380109786987 seconds null
took 0.50135898590088 seconds unset
took 0.50358104705811 seconds null
took 0.50115609169006 seconds unset
PHP Version: 7.0.33
took 0.50918698310852 seconds null
took 0.50490307807922 seconds unset
took 0.50227618217468 seconds null
took 0.50514912605286 seconds unset
PHP Version: 5.6.40
took 1.0063569545746 seconds null
took 1.6303179264069 seconds unset
took 1.0689589977264 seconds null
took 1.6382601261139 seconds unset
PHP Version: 5.4.45
took 1.0791940689087 seconds null
took 1.6308979988098 seconds unset
took 1.0029168128967 seconds null
took 1.6320278644562 seconds unset
But, with other example:
<?php
ini_set("memory_limit", "512M");
echo "PHP Version: " . phpversion() . PHP_EOL . PHP_EOL;
$start = microtime(true);
$arr = [];
for ($i = 0; $i < 1000000; $i++) {
$arr[] = 'a';
}
$arr = null;
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds" . PHP_EOL;
$start = microtime(true);
$arr = [];
for ($i = 0; $i < 1000000; $i++) {
$arr[] = 'a';
}
unset($arr);
$elapsed = microtime(true) - $start;
echo "took $elapsed seconds" . PHP_EOL;
Results:
PHP Version: 7.4.8
took 0.053696155548096 seconds
took 0.053897857666016 seconds
PHP Version: 7.3.20
took 0.054572820663452 seconds
took 0.054342031478882 seconds
PHP Version: 7.2.32
took 0.05678391456604 seconds
took 0.057311058044434 seconds
PHP Version: 7.1.33
took 0.097366094589233 seconds
took 0.073100090026855 seconds
PHP Version: 7.0.33
took 0.076443910598755 seconds
took 0.077098846435547 seconds
PHP Version: 7.0.33
took 0.075634002685547 seconds
took 0.075317859649658 seconds
PHP Version: 5.6.40
took 0.29681086540222 seconds
took 0.28199100494385 seconds
PHP Version: 5.4.45
took 0.30513095855713 seconds
took 0.29265689849854 seconds
I still doubt about this, but I've tried it at my script and I'm using xdebug to know how it will affect my app memory usage.
The script is set on my function like this :
function gen_table_data($serv, $coorp, $type, $showSql = FALSE, $table = 'ireg_idnts') {
$sql = "SELECT COUNT(`operator`) `operator` FROM $table WHERE $serv = '$coorp'";
if($showSql === FALSE) {
$sql = mysql_query($sql) or die(mysql_error());
$data = mysql_fetch_array($sql);
return $data[0];
} else echo $sql;
}
And I add unset just before the return code and it give me : 160200
then I try to change it with $sql = NULL and it give me : 160224 :)
But there is something unique on this comparative when I am not using unset() or NULL, xdebug give me 160144 as memory usage
So, I think giving line to use unset() or NULL will add process to your application and it will be better to stay origin with your code and decrease the variable that you are using as effective as you can .
Correct me if I'm wrong, thanks
I created a new performance test for unset and =null, because as mentioned in the comments the here written has an error (the recreating of the elements).
I used arrays, as you see it didn't matter now.
<?php
$arr1 = array();
$arr2 = array();
for ($i = 0; $i < 10000000; $i++) {
$arr1[$i] = 'a';
$arr2[$i] = 'a';
}
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$arr1[$i] = null;
}
$elapsed = microtime(true) - $start;
echo 'took '. $elapsed .'seconds<br>';
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
unset($arr2[$i]);
}
$elapsed = microtime(true) - $start;
echo 'took '. $elapsed .'seconds<br>';
But i can only test it on an PHP 5.5.9 server, here the results:
- took 4.4571571350098 seconds
- took 4.4425978660583 seconds
I prefer unset for readability reasons.
unset code if not freeing immediate memory is still very helpful and would be a good practice to do this each time we pass on code steps before we exit a method. take note its not about freeing immediate memory.
immediate memory is for CPU, what about secondary memory which is RAM.
and this also tackles about preventing memory leaks.
please see this link
http://www.hackingwithphp.com/18/1/11/be-wary-of-garbage-collection-part-2
i have been using unset for a long time now.
better practice like this in code to instanly unset all variable that have been used already as array.
$data['tesst']='';
$data['test2']='asdadsa';
....
nth.
and just unset($data); to free all variable usage.
please see related topic to unset
How important is it to unset variables in PHP?
[bug]

Categories