Почему вызов функции (такой как strlen, count и т. Д.) Для указанного значения так медленен?
Я только что нашел что-то очень странное в 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
и для пользовательских функций.
В чем дело?
3 ответа
Я нашел отчет об ошибке 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 и понятия не имел об этой проблеме.
К счастью, существует простой обходной путь, который применим во многих случаях - используйте временную локальную переменную внутри цикла и скопируйте в ссылочную переменную в конце.
Таким образом, принимая ответ, который вы уже дали, вы можете частично избежать этой проблемы, форсировав копию перед итеративной работой (копирование обратно после изменения данных).
<?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
Не так хорошо, но чертовски лучше.
Это больше не проблема (PHP 7.4.0):
0.083219051 seconds (no ref)
0.090487003 seconds (ref)
0.091565132 seconds (ref+copy)
(Чуть большие массивы и итераций 10000000)