Why is calling a function (such as strlen, count etc) on a referenced value so slow?

I found a bug report from 2005 that describes exactly this issue: http://bugs.php.net/bug.php?id=34540

So the problem seems to be that when passing a referenced value to a function that doesn't accept a reference, PHP needs to copy it.

This can be demonstrated with this test code:

<?php
function CalledFunc(&$aData)
{
    // Do nothing
}

function TestFunc(&$aArray)
{
    $aArray = range(0, 100000);
    $fStartTime = microtime(true);

    for ($iIter = 0; $iIter < 1000; $iIter++)
    {
        CalledFunc($aArray);
    }

    $fTaken = microtime(true) - $fStartTime;

    print "took $fTaken seconds\n";
}

$aArray = array();
TestFunc($sData);
?>

This runs quickly, but if you change function CalledFunc(&$aData) to function CalledFunc($aData) you'll see a similar slow-down to the count example.

This is rather worrying, since I've been coding PHP for quite a while and I had no idea about this issue.

Fortunately there's a simple workaround that is applicable in many cases - use a temporary local variable inside the loop, and copy to the reference variable at the end.


So, taking your answer already given, you can partially avoid this issue by forcing the copy before iterative work (Copying back afterward if the data is changed).

<?php
function TestCountNon($aArray)
{
    $aArray = range(0, 100000);
    $fStartTime = microtime(true);
    for ($iIter = 0; $iIter < 1000; $iIter++)
    {
        $iCount = count($aArray);
    }
    $fTaken = microtime(true) - $fStartTime;

    print "Non took $fTaken seconds\n<br>";
}

function TestCount(&$aArray)
{
    $aArray = range(0, 100000);
    $fStartTime = microtime(true);
    for ($iIter = 0; $iIter < 1000; $iIter++)
    {
        $iCount = count($aArray);
    }
    $fTaken = microtime(true) - $fStartTime;

    print "took $fTaken seconds\n<br>";
}

function TestCountA(&$aArray)
{
    $aArray = range(0, 100000);
    $fStartTime = microtime(true);
    $bArray = $aArray;
    for ($iIter = 0; $iIter < 1000; $iIter++)
    {
        $iCount = count($bArray);
    }
    $aArray = $bArray;
    $fTaken = microtime(true) - $fStartTime;

    print "A took $fTaken seconds\n<br>";
}

$nonArray = array();
TestCountNon($nonArray);

$aArray = array();
TestCount($aArray);

$bArray = array();
TestCountA($bArray);
?>

Results are:

Non took 0.00090217590332031 seconds 
took 17.676940917969 seconds 
A took 0.04144287109375 seconds 

Not quite as good, but a damn lot better.