Производительность PHP: копирование и ссылка - PullRequest
8 голосов
/ 28 октября 2010

Привет.Сегодня я написал небольшой тестовый скрипт для сравнения производительности копирования переменных и создания ссылок на них.Я ожидал, что создание ссылок на большие массивы, например, будет значительно медленнее, чем копирование всего массива.Вот мой тестовый код:

<?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 ]

17 голосов
/ 28 октября 2010

PHP, скорее всего, реализует copy-on-write для своих массивов, то есть, когда вы «копируете» массив, 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);
}
3 голосов
/ 26 июля 2011

Вам не нужно (и, следовательно, не следует) присваивать или передавать переменные по ссылке только из соображений производительности. 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

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

3 голосов
/ 28 октября 2010

в recursiveReference, который вы вызываете recursiveCopy ... это не имеет никакого смысла, в этом случае вы вызываете recursiveReference только один раз.исправьте свой код, снова выполните тест и вернитесь с новыми результатами.

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

1 голос
/ 28 октября 2010

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

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

(тем не менее, как уже отмечали другие, ваш пример кода не совсем так, как вы думаете;))

0 голосов
/ 28 октября 2010

recursiveReference вызывает recursiveCopy. Не то чтобы это обязательно повлияло на производительность, но это, вероятно, не то, что вы пытаетесь сделать.

Не знаю, почему производительность ниже, но она не отражает измерения, которые вы пытаетесь выполнить.

0 голосов
/ 28 октября 2010
  1. В функции recursiveReference () вы вызываете функцию recursiveCopy (). Это то, что вы действительно хотели сделать?
  2. Вы ничего не делаете с переменной $ foo - возможно, она должна была использоваться в дальнейшем вызове метода?
  3. Передача переменной по ссылке должна, как правило, экономить память стека в случае передачи больших объектов.
...