Производительность PHP: копирование и ссылка
Привет. Сегодня я написал небольшой тестовый скрипт для сравнения производительности копирования переменных и создания ссылок на них. Я ожидал, что создание ссылок на большие массивы, например, будет значительно медленнее, чем копирование всего массива. Вот мой контрольный код:
<?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);
фактический результат, который я получил, был, что recursiveReference заняло около 20 раз (!) пока recursiveCopy.
может кто-нибудь объяснить это поведение PHP?
6 ответов
PHP, скорее всего, реализует копирование при записи для своих массивов, то есть когда вы "копируете" массив, PHP не выполняет всю работу по физическому копированию памяти, пока вы не измените одну из копий, и ваши переменные больше не смогут ссылаться на одно и то же внутреннее представление.
ваш бенчмаркинг поэтому принципиально ошибочен, так как ваш recursiveCopy
функция фактически не копирует объект; если бы это было так, у вас очень быстро закончилась бы память.
попробовать это: назначая элементу массива, вы заставляете PHP на самом деле создать копию. Вы обнаружите, что у вас заканчивается память довольно быстро, поскольку ни одна из копий не выходит из области (и не собирается мусор), пока рекурсивная функция не достигнет максимальной глубины.
function recursiveCopy($array, $count) {
if($count === 1000)
return;
$foo = $array;
$foo[9492] = 3; // Force PHP to copy the array
recursiveCopy($array, $count+1);
}
в recursiveReference вы вызываете recursiveCopy... это не имеет никакого смысла, в этом случае вы вызываете recursiveReference только один раз. исправьте свой код, снова запустите тест и вернитесь с новыми результатами.
кроме того, я не думаю, что это полезно для ориентира, чтобы сделать это рекурсивно. лучшим решением было бы вызвать функцию 1000 раз в цикле-один раз с массивом напрямую и один со ссылкой на этот массив.
вам не нужно (и, следовательно, не должно) назначать или передавать переменные по ссылке только по соображениям производительности. PHP делает такие оптимизации автоматически.
тест, который вы выполнили, имеет недостатки из-за этих автоматических оптимизаций. Вместо этого запустили следующий тест:
<?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";
?>
Это был выход:
//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
Как вы можете видеть, нет существенной разницы в производительности при назначении по ссылке, пока вы фактически не напишете копию, т. е. когда также является функциональным отличием.
вообще говоря, в PHP вызов по ссылке-это не то, что вы сделали бы по соображениям производительности; это то, что вы сделали бы по функциональным причинам, т. е. потому что вы действительно хотите, чтобы указанная переменная была обновлена.
Если у вас нет функциональной причины для вызова по ссылке, вы должны придерживаться регулярной передачи параметров, потому что PHP обрабатывает вещи совершенно эффективно таким образом.
(тем не менее, как указывали другие, Ваш пример кода в любом случае, это не совсем то, что вы думаете ;))
- в функции recursiveReference() вы вызываете функцию recursiveCopy (). Это то, что вы действительно намеревались сделать?
- вы ничего не делаете с переменной $foo-вероятно, она должна была использоваться в дальнейшем вызове метода?
- передача переменной по ссылке обычно должна сохранять память стека в случае передачи больших объектов.
recursiveReference вызывает recursiveCopy. Не то чтобы это обязательно повредило бы производительности, но это, вероятно, не то, что вы пытаетесь сделать.
Не уверен, почему производительность медленнее, но она не отражает измерение, которое вы пытаетесь сделать.