Що краще у звільненні пам'яті за допомогою PHP: unset () або $ var = null


244

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

Чи є кращий, і які міркування?

Відповіді:


234

Про це згадувалося на сторінці незавершеного посібника у 2009 році :

unset()робить лише те, що говорить його назва - 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) змінну, буде спровокована помилка і значення для виразу змінної буде нульовим. (Тому що, що ще потрібно робити PHP? Кожен вираз повинен мати певне значення.)
  • Змінна з присвоєним їй null все ж є абсолютно нормальною змінною.

18
Зауважте, що якщо $whateverвказує на об'єкт, $whatever = nullперезаписує вказівник, а не сам об’єкт, тому він діє в основному так само, як unset().
Гра "Двомісний"

1
@VonC: невстановлена ​​цитата на php.net, на яку ви посилаєтесь, більше не існує.
Юрген Телен

@ JürgenThelen правда, але зміст цієї старої відповіді все ще здається актуальним, ні?
VonC

1
@VonC: Однозначно. Я просто не впевнений, що "цикли процесора не потрібні" і "раніше .. з пам'яті" запускає збирання сміття. Дивіться stackoverflow.com/q/20230626/693207 . Може, ти можеш пролити трохи світла?
Юрген Телен

1
@Omar Я відредагував відповідь: Сторінка "unset man" з 2009 року (я пов’язана з версією 2009 року) містить розділ, який більше не присутній у поточній версії цієї ж сторінки.
VonC

48

unsetнасправді не функція, а мовна конструкція . Це не більше виклику функції, ніж a returnабо an include.

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


Тому я завжди їх використовував, особисто я вважав, що вони виглядають краще, ніж $ var = null. До речі, я завжди використовував NULL повні ковпачки ... але тепер я не знаю чому?
alex

1
@VonC: Так, я це зрозумів, але чому ви можете використовувати малі істинні, неправдиві та нульові значення?
alex

3
@alex, ти можеш це зробити з відключеним. Наприклад "$ test = 4; (unset) $ test;" - дивним, але правдивим, і він повертає значення $ test перед його скиданням. Незважаючи на те, керівництво PHP підтверджує, що це мовна конструкція.
thomasrutter

5
@alex: PSR-2 вимагає малих літер для всіх ключових слів.
Тгр

2
@alex - PHP-слова не залежать від регістру; Ви також можете писати unsetяк UnSeT, наприклад. Громада влаштувалася на загальні регіони як питання стилю, але інші корпуси все ще працюють.
Марк Рід

35

Зробивши unset () для змінної, ви по суті позначили цю змінну "сміття" (у PHP насправді немає, але, наприклад,), тому пам'ять не одразу доступна. Змінна більше не містить дані, але стек залишається на більшій величині. Здійснення нульового методу опускає дані та скорочує пам'ять стеку майже відразу.

Це було з особистого досвіду та інших. Дивіться коментарі функції unset () тут .

Я особисто використовую unset () між ітераціями в циклі, так що мені не потрібно, щоб затримка стека була розміром yo-yo'd. Даних немає, але слід залишається. При наступній ітерації, php вже займає пам'ять, і, таким чином, швидше ініціалізувати наступну змінну.


15
Встановлення чогось NULL може принести користь, якщо пам'ять, необхідна для утримування значення NULL, менша, ніж потрібна для зберігання будь-якого значення, яке воно раніше містило. Наприклад, довга струна. Якщо рядок не був постійним, а його посилання знижується до нуля, то цю пам'ять слід звільнити. Unset чистіший - він більше не підтримує посилання. Вам доведеться чекати збору сміття, але безпечно ставитися до цього, оскільки він не займає пам'яті, оскільки низький стан пам'яті призведе до збору сміття.
thomasrutter

ми не можемо використовувати обидва? дорівнює нулю, а потім відмінено?
Набіель Хан

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";
?>

Зважаючи на це, здається, що "= null" швидше.

Результати 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
Knyri

4
@ansur, завжди зателефонуйте gc_collect_cyclesперед запуском таймера, щоб отримати більш точні результати.
Пейсьєр

@knyri Ви можете будь-ласка, посилатися на це?
Набіл Хан

@NabeelKhan У мене більше немає результатів цього тесту; але в моєму попередньому коментарі є посилання на тестовий код.
Кнірі

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";

ключ більше не існує.


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
$ a = 78; $ b = $ a; unset ($ a); var_dump ($ b); // 78; var_dump ($ a); // Невизначена змінна: a
zloctb

13

Що стосується об'єктів, особливо у сценарії ледачих навантажень, слід врахувати, що збирач сміття працює в режимі простою процесорних циклів, тому припускаючи, що ви потрапляєте у проблеми, коли багато об’єктів завантажують невеликі часові штрафи, це вирішить звільнення пам'яті.

Використовуйте time_nanosleep, щоб GC могла збирати пам'ять. Встановити змінну на null бажано.

Випробуваний на виробничому сервері, спочатку робота споживала 50 Мб, а потім була зупинена. Після використання наноспання 14МБ стало постійним споживанням пам'яті.

Слід сказати, що це залежить від поведінки GC, яка може змінюватися від версії PHP до версії. Але це працює на PHP 5.3 штрафу.

напр. цей зразок (код взятий з google-каналу VirtueMart2)

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);
    ...

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] в локальну змінну, встановіть масив на нуль і поверніть локальну змінну. Хороший фон тут: 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 секунд


1

unsetкод, якщо не звільнення негайної пам'яті, як і раніше дуже корисний, і це було б хорошою практикою робити це кожен раз, коли ми передаємо кроки коду, перш ніж вийти з методу. врахуйте його не про звільнення негайної пам'яті. безпосередня пам'ять призначена для процесора, як щодо вторинної пам'яті, яка є ОЗП.

і це також стосується запобігання витоку пам'яті.

перегляньте це посилання http://www.hackingwithphp.com/18/1/11/be-wary-of-garbage-collection-part-2

я вже давно користуюся невстановленою.

краща практика, як це в коді, щоб миттєво скинути всі змінні, які вже використовувались як масив.

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

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

дивіться пов’язану тему, щоб скасувати

Наскільки важливо скинути змінні в PHP?

[помилка]


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)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.