Почему вызов функции (такой как strlen, count и т. Д.) Для ссылочного значения такой медленный? - PullRequest
12 голосов
/ 25 июня 2010

Я только что нашел что-то очень странное в PHP.

Если я передаю переменную в функцию по ссылке, а затем вызываю для нее функцию, это невероятно медленно.

Если вы перебираете внутренний вызов функции, а переменная имеет большой размер, она может быть на много порядков медленнее, чем если переменная передается по значению.

Пример:

<?php
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";
}

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

На моем компьютере постоянно требуется около 20 секунд (в PHP 5.3).

Но если я изменю функцию на передачу по значению (т.е. function TestCount($aArray) вместо function TestCount(&$aArray)), то она будет работать примерно за 2 мс - буквально в 10000 раз быстрее !

То же самое верно для других встроенных функций, таких как strlen, и для пользовательских функций.

Что происходит?

Ответы [ 2 ]

13 голосов
/ 25 июня 2010

Я нашел отчет об ошибке 2005 года, который описывает именно эту проблему: http://bugs.php.net/bug.php?id=34540

Таким образом, проблема заключается в том, что при передаче ссылочного значения в функцию, которая не принимает ссылку, PHP должен ее скопировать.

Это можно продемонстрировать с помощью этого кода теста:

<?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);
?>

Это выполняется быстро, но если вы измените function CalledFunc(&$aData) на function CalledFunc($aData), то увидите замедление, аналогичное примеру count.

Это довольно тревожно, так как я уже довольно давно кодирую PHP и понятия не имел об этой проблеме.

К счастью, существует простой обходной путь, который применим во многих случаях - используйте временную локальную переменную внутри цикла и скопируйте в ссылочную переменную в конце.

1 голос
/ 25 июня 2010

Таким образом, принимая ответ, который вы уже дали, вы можете частично избежать этой проблемы, форсировав копию перед итеративной работой (Копирование обратно после изменения данных).

<?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);
?>

Результаты:

Non took 0.00090217590332031 seconds 
took 17.676940917969 seconds 
A took 0.04144287109375 seconds 

Не так хорошо, но чертовски лучше.

...