Что лучше при освобождении памяти с помощью PHP: unset() или $var = null

Я понимаю, что второй избегает накладных расходов при вызове функции (обновление, на самом деле, является языковой конструкцией), но было бы интересно узнать, лучше ли одно, чем другое. Я использую unset() для большей части моего кода, но я недавно просмотрел несколько респектабельных классов, найденных в сети, которые используют $var = null вместо.

Есть ли предпочтительный, и в чем причина?

12 ответов

Решение

Это было упомянуто на нерабочей странице руководства в 2009 году:

unset() делает только то, что говорит его имя - сбросить переменную. Это не вызывает немедленного освобождения памяти. Сборщик мусора в PHP сделает это, когда посчитает нужным - преднамеренно, так как эти циклы ЦП в любом случае не нужны, или так поздно, как сценарию не хватило бы памяти, что бы ни произошло первым.

Если вы делаете $whatever = null; тогда вы переписываете данные переменной. Вы можете освободить / сжать память быстрее, но это может украсть циклы ЦП из кода, который действительно в них нуждается, быстрее, что приведет к увеличению общего времени выполнения.

(С 2013 года, что unset страница man больше не включает этот раздел)

Обратите внимание, что до php5.3, если у вас есть два объекта в циклической ссылке, например, в родительско-дочерних отношениях, вызов unset() для родительского объекта не освободит память, используемую для родительской ссылки в дочернем объекте. (Также память не будет освобождена, когда родительский объект будет собран мусором.) ( Ошибка 33595)


Вопрос " разница между unset и = null" детализирует некоторые различия:


unset($a) также удаляет $a из таблицы символов; например:

$a = str_repeat('hello world ', 100);
unset($a);
var_dump($a);

Выходы:

Notice: Undefined variable: a in xxx
NULL

Но когда $a = null используется:

$a = str_repeat('hello world ', 100);
$a = null;
var_dump($a);
Outputs:

NULL

Кажется, что $a = null немного быстрее, чем его unset() аналог: обновление записи таблицы символов происходит быстрее, чем ее удаление.


  • при попытке использовать несуществующий (unset) переменная, ошибка будет вызвана, и значение для выражения переменной будет нулевым. (Потому что, что еще должен делать PHP? Каждое выражение должно приводить к некоторому значению.)
  • Переменная с присвоенным ей нулем все еще остается совершенно нормальной переменной.

unset на самом деле это не функция, а языковая конструкция. Это не более вызов функции, чем return или include,

Помимо проблем с производительностью, используя unset делает ваш код гораздо понятнее.

Выполнив unset() для переменной, вы, по сути, пометили переменную как "сборщик мусора" (в действительности у PHP такой нет, но, например, ради), поэтому память не сразу доступна. Переменная больше не хранит данные, но стек остается большего размера. Выполнение нулевого метода удаляет данные и практически сразу сокращает объем стека.

Это было из личного опыта и других. Смотрите комментарии функции unset() здесь.

Лично я использую unset() между итерациями в цикле, так что мне не нужно, чтобы задержка стека была равна размеру йо-йо. Данные ушли, но след остается. На следующей итерации php уже забирает память и, следовательно, быстрее инициализирует следующую переменную.

<?php
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    $a = NULL;
}
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds\r\n";



$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    unset($a);
}
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds\r\n";
?>

Похоже, что "= ноль" быстрее.

Результаты PHP 5.4:

  • заняло 0,88389301300049 секунд
  • заняло 2,1757180690765 секунд

Результаты PHP 5.3:

  • заняло 1.7235369682312 секунд
  • заняло 2.9490959644318 секунд

Результаты PHP 5.2:

  • заняло 3.0069220066071 секунд
  • заняло 4.7002630233765 секунд

Результаты PHP 5.1:

  • заняло 2,6272349357605 секунд
  • заняло 5,0403649806976 секунд

Вещи начинают выглядеть по-другому с PHP 5.0 и 4.4.

5,0:

  • заняло 10.038941144943 секунд
  • заняло 7.0874409675598 секунд

4,4:

  • заняло 7,5352551937103 секунд
  • заняло 6,6245851516724 секунд

Имейте в виду, что microtime(true) не работает в PHP 4.4, поэтому мне пришлось использовать пример microtime_float, приведенный в php.net/microtime / Example #1.

Он работает по-другому для переменных, скопированных по ссылке:

$a = 5;
$b = &$a;
unset($b); // just say $b should not point to any variable
print $a; // 5

$a = 5;
$b = &$a;
$b = null; // rewrites value of $b (and $a)
print $a; // nothing, because $a = null

Это имеет значение с элементами массива.

Рассмотрим этот пример

$a = array('test' => 1);
$a['test'] = NULL;
echo "Key test ", array_key_exists('test', $a)? "exists": "does not exist";

Здесь ключ 'test' все еще существует. Однако в этом примере

$a = array('test' => 1);
unset($a['test']);
echo "Key test ", array_key_exists('test', $a)? "exists": "does not exist";

ключ больше не существует

Что касается объектов, особенно в сценарии с отложенной загрузкой, следует учитывать, что сборщик мусора работает в незанятых циклах ЦП, поэтому предположим, что вы столкнетесь с проблемами при загрузке большого количества объектов с небольшим временным штрафом, что приведет к освобождению памяти.

Используйте time_nanosleep, чтобы позволить GC собирать память. Установка переменной в null желательна.

Протестировано на рабочем сервере, первоначально работа потребляла 50 МБ, а затем была остановлена. После того, как был использован Nanosleep, 14 МБ потребляли постоянную память.

Следует сказать, что это зависит от поведения GC, которое может меняться от версии PHP к версии. Но это работает на PHP 5.3 нормально.

например. этот образец (код взят из VirtueMart2 в фиде Google)

for($n=0; $n<count($ids); $n++)
{
    //unset($product); //usefull for arrays
    $product = null
    if( $n % 50 == 0 )
    {
        // let GC do the memory job
        //echo "<mem>" . memory_get_usage() . "</mem>";//$ids[$n];
        time_nanosleep(0, 10000000);
    }

    $product = $productModel->getProductSingle((int)$ids[$n],true, true, true);
    ...

PHP 7 уже работал над такими проблемами управления памятью и его сокращение до минимального использования.

<?php
  $start = microtime(true);
  for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    $a = NULL;
  }
  $elapsed = microtime(true) - $start;

  echo "took $elapsed seconds\r\n";

  $start = microtime(true);
  for ($i = 0; $i < 10000000; $i++) {
     $a = 'a';
     unset($a);
  }
  $elapsed = microtime(true) - $start;

  echo "took $elapsed seconds\r\n";

?>

PHP 7.1 Outpu:

Потребовалось 0,16778993606567 секунд. Потребовалось 0,16630101203918 секунд.

Для записи, и исключая время, которое требуется:

<?php
echo "<hr>First:<br>";
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 
echo "<hr>Unset:<br>";
unset($x);
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 
echo "<hr>Null:<br>";
$x=null;
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n";

echo "<hr>function:<br>";
function test() {
    $x = str_repeat('x', 80000);
}
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 

echo "<hr>Reasign:<br>";
$x = str_repeat('x', 80000);
echo memory_get_usage() . "<br>\n";      
echo memory_get_peak_usage() . "<br>\n"; 

Возвращается

First:
438296
438352
Unset:
438296
438352
Null:
438296
438352
function:
438296
438352
Reasign:
438296
520216 <-- double usage.

Вывод, как нулевой, так и неустановленной свободной памяти, как и ожидалось (не только в конце выполнения). Кроме того, переназначение переменной содержит значение дважды в некоторой точке (520216 против 438352)

Пример кода из комментария

echo "PHP Version: " . phpversion() . PHP_EOL . PHP_EOL;

$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    $a = NULL;
}
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds" . PHP_EOL;



$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $a = 'a';
    unset($a);
}
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds" . PHP_EOL;

Запуск в контейнере докеров из образаphp:7.4-fpm и другие..

PHP Version: 7.4.8
took 0.22569918632507 seconds null
took 0.11705803871155 seconds unset
took 0.20791196823121 seconds null
took 0.11697316169739 seconds unset

PHP Version: 7.3.20
took 0.22086310386658 seconds null
took 0.11882591247559 seconds unset
took 0.21383500099182 seconds null
took 0.11916995048523 seconds unset

PHP Version: 7.2.32
took 0.24728178977966 seconds null
took 0.12719893455505 seconds unset
took 0.23839902877808 seconds null
took 0.12744522094727 seconds unset

PHP Version: 7.1.33
took 0.51380109786987 seconds null
took 0.50135898590088 seconds unset
took 0.50358104705811 seconds null
took 0.50115609169006 seconds unset

PHP Version: 7.0.33
took 0.50918698310852 seconds null
took 0.50490307807922 seconds unset
took 0.50227618217468 seconds null
took 0.50514912605286 seconds unset

PHP Version: 5.6.40
took 1.0063569545746 seconds null
took 1.6303179264069 seconds unset
took 1.0689589977264 seconds null
took 1.6382601261139 seconds unset

PHP Version: 5.4.45
took 1.0791940689087 seconds null
took 1.6308979988098 seconds unset
took 1.0029168128967 seconds null
took 1.6320278644562 seconds unset

Но с другим примером:

<?php
ini_set("memory_limit", "512M");

echo "PHP Version: " . phpversion() . PHP_EOL . PHP_EOL;

$start = microtime(true);
$arr = [];
for ($i = 0; $i < 1000000; $i++) {
    $arr[] = 'a';
}
$arr = null;
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds" . PHP_EOL;



$start = microtime(true);
$arr = [];
for ($i = 0; $i < 1000000; $i++) {
    $arr[] = 'a';
}
unset($arr);
$elapsed = microtime(true) - $start;

echo "took $elapsed seconds" . PHP_EOL;

Полученные результаты:

PHP Version: 7.4.8
took 0.053696155548096 seconds
took 0.053897857666016 seconds

PHP Version: 7.3.20
took 0.054572820663452 seconds
took 0.054342031478882 seconds

PHP Version: 7.2.32
took 0.05678391456604 seconds
took 0.057311058044434 seconds


PHP Version: 7.1.33
took 0.097366094589233 seconds
took 0.073100090026855 seconds

PHP Version: 7.0.33
took 0.076443910598755 seconds
took 0.077098846435547 seconds

PHP Version: 7.0.33
took 0.075634002685547 seconds
took 0.075317859649658 seconds

PHP Version: 5.6.40
took 0.29681086540222 seconds
took 0.28199100494385 seconds

PHP Version: 5.4.45
took 0.30513095855713 seconds
took 0.29265689849854 seconds

Я все еще сомневаюсь в этом, но я попробовал это в своем сценарии, и я использую xdebug, чтобы знать, как это повлияет на использование памяти моего приложения. Сценарий установлен на моей функции следующим образом:

function gen_table_data($serv, $coorp, $type, $showSql = FALSE, $table = 'ireg_idnts') {
    $sql = "SELECT COUNT(`operator`) `operator` FROM $table WHERE $serv = '$coorp'";
    if($showSql === FALSE) {
        $sql = mysql_query($sql) or die(mysql_error());
        $data = mysql_fetch_array($sql);
        return $data[0];
    } else echo $sql;
}

И я добавляю unset незадолго до return код, и это дает мне: 160200, то я пытаюсь изменить его с $sql = NULL и это даст мне: 160224:)

Но есть что-то уникальное в этом сравнительном, когда я не использую unset() или NULL, xdebug дает мне 160144 в качестве использования памяти

Итак, я думаю, что указание строки для использования unset() или NULL добавит процесс к вашему приложению, и будет лучше оставаться исходным с вашим кодом и уменьшать используемую переменную настолько эффективно, насколько это возможно.

Поправьте меня если я не прав, спасибо

Я создал новый тест производительности для unset а также =nullпотому что, как упоминалось в комментариях, здесь написано ошибка (воссоздание элементов). Я использовал массивы, как вы видите, теперь это не имело значения.

<?php
$arr1 = array();
$arr2 = array();
for ($i = 0; $i < 10000000; $i++) {
    $arr1[$i] = 'a';
    $arr2[$i] = 'a';
}

$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    $arr1[$i] = null;
}
$elapsed = microtime(true) - $start;

echo 'took '. $elapsed .'seconds<br>';

$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
    unset($arr2[$i]);
}
$elapsed = microtime(true) - $start;

echo 'took '. $elapsed .'seconds<br>';

Но я могу только проверить это на сервере PHP 5.5.9, вот результаты: - заняло 4.4571571350098 секунд - заняло 4.4425978660583 секунд

я предпочитаю unset для удобства чтения.

unset код, если не освобождает немедленную память, все еще очень полезен и будет хорошей практикой делать это каждый раз, когда мы передаем шаги кода перед выходом из метода. обратите внимание, это не об освобождении немедленной памяти. Непосредственная память для процессора, а как насчет вторичной памяти, которая является ОЗУ.

и это также решает проблему предотвращения утечек памяти.

пожалуйста, смотрите эту ссылку http://www.hackingwithphp.com/18/1/11/be-wary-of-garbage-collection-part-2

Я давно использую unset.

Лучше практиковать подобное в коде, чтобы мгновенно сбрасывать все переменные, которые уже использовались в качестве массива.

$data['tesst']='';
$data['test2']='asdadsa';
....
nth.

а также just unset($data); освободить все переменные использования.

пожалуйста, смотрите связанную тему, чтобы сбросить

Насколько важно сбросить переменные в PHP?

[Ошибка]

Другие вопросы по тегам