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

244

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

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

Алекс
источник

Ответы:

234

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

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

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

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

Обратите внимание, что до 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переменную non-existing ( ), возникает ошибка, и значение для выражения переменной будет нулевым. (Потому что, что еще должен делать PHP? Каждое выражение должно приводить к некоторому значению.)
  • Переменная с присвоенным ей нулем все еще остается совершенно нормальной переменной.
VonC
источник
18
Обратите внимание, что если $whateverуказывает на объект, $whatever = nullперезаписывает указатель, а не сам объект, поэтому он действует в основном так же, как unset().
Гра Двойной
1
@VonC: неустановленная цитата на php.net, на которую вы ссылаетесь, больше не существует.
Юрген Телен
@ JürgenThelen правда, но содержание этого старого ответа все еще кажется актуальным, нет?
VonC
1
@VonC: определенно. Я просто не уверен в том, что «циклы ЦПУ не нужны» и «раньше ... не хватает памяти» запускает сборку мусора. См. Stackoverflow.com/q/20230626/693207 . Может быть, вы можете пролить свет?
Юрген Телен
1
@ Омар. Я отредактировал ответ: на неустановленной странице руководства 2009 года (я ссылался на версию 2009 года) действительно есть раздел, которого больше нет в текущей версии этой же страницы.
VonC
48

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

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

Алекс Барретт
источник
Вот почему я всегда использовал их, лично я думал, что они выглядят лучше, чем $ var = null. Кстати, я всегда использовал NULL полные заглавные буквы ... но теперь я не знаю почему?
Алекс
1
@VonC: Да, я понял это, но почему вы можете использовать строчные буквы true, false и null?
Алекс
3
@alex, вы можете сделать это с помощью unset. Например, "$ test = 4; (unset) $ test;" - странно, но факт, и он возвращает значение $ test до его сброса. Несмотря на это, руководство по PHP подтверждает, что это языковая конструкция.
Томасруттер
5
@alex: PSR-2 требует строчных букв для всех ключевых слов.
Tgr
2
@alex - ключевые слова PHP нечувствительны к регистру; Вы могли бы также записать unsetкак UnSeT, например. Сообщество остановилось на строчных буквах как вопрос стиля, но другие оболочки все еще работают.
Марк Рид
35

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

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

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

Уильям Холройд
источник
15
Установка чего-либо в NULL может быть полезной, если память, необходимая для хранения значения NULL, меньше, чем та, которая требуется для хранения любого значения, которое было ранее. Например, длинная строка. Если строка не была константой и ее счетчик ссылок падает до нуля, то эта память должна быть освобождена. Unset чище - он больше не поддерживает ссылку. Вам нужно дождаться сбора мусора, но можно с уверенностью считать, что он не занимает память, так как из-за нехватки памяти вызовется сборка мусора.
Томасруттер
мы не можем использовать оба? равно нулю, а затем не установлен?
Набиль Хан
2
@NabeelKhan Я бы предложил использовать unset () внутри циклов, а затем обнулять его при выходе из цикла. В противном случае это влияет на производительность, выполняя оба цикла. Если вы не используете циклы, просто обнулите, поскольку это уже делает логику unset () за кадром.
Уильям Холройд
27
<?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.


источник
7
Я думаю, что ваш тест ошибочен. Первый цикл - это простое переназначение, а второй цикл уничтожает и воссоздает один и тот же символ. Если тест переделан с массивом unsetбыстрее. У меня есть тест, который позже проверяет наличие в unsetделе. В этом тесте он nullнемного быстрее. Тест: pastebin.com/fUe57C51
Книри
4
@ansur, всегда звоните gc_collect_cyclesперед запуском таймера, чтобы получить более точные результаты.
Pacerier
@knyri, пожалуйста, ссылку на это?
Набиль Хан
@NabeelKhan У меня больше нет результатов этого теста; но есть ссылка на тестовый код в моем предыдущем комментарии.
Knyri
19

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

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

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

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

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

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

Auris
источник
18

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

$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
Риад
источник
5
Я кодирую php уже несколько лет и никогда не видел "&" по поводу ссылки на оригинальный var. Спасибо + 1 :)
Крис
1
$ а = 78; $ B = $ а; снята с охраны ($ а); var_dump ($ б); // 78; var_dump ($ a); // Неопределенная переменная: a
zloctb
13

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

Используйте 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);
    ...
OSP
источник
3

Я все еще сомневаюсь в этом, но я попробовал это в своем сценарии, и я использую 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 добавит процесс к вашему приложению, и будет лучше оставаться исходным с вашим кодом и уменьшать используемую вами переменную настолько эффективно, насколько это возможно.

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

Энджи Азиз
источник
Я думаю, что пока вы возвращаете элемент $ data [0], на весь массив ссылаются / но это всего лишь гипотеза. Попробуйте скопировать $ data [0] в локальную переменную, установить массив в null и вернуть локальную переменную. Хороший фон здесь tuxradar.com/practicalphp/18/1/11 и ofc. php.net/manual/en/features.gc.php
OSP
2

Я создал новый тест производительности для 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для удобства чтения.

Майкл Б.
источник
2

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 секунд

Swapnil
источник
1

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?

[Ошибка]

zero8
источник
1

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

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

Магеллана
источник