Co jest lepszego w zwalnianiu pamięci za pomocą PHP: unset () lub $ var = null

244

Zdaję sobie sprawę, że drugi unika narzutu wywołania funkcji ( aktualizacja , to właściwie konstrukcja językowa), ale byłoby interesujące wiedzieć, czy jedno jest lepsze od drugiego. Używam unset()przez większość mojego kodowania, ale ostatnio przejrzałem kilka godnych szacunku klas znalezionych w sieci, które $var = nullzamiast tego używają .

Czy istnieje preferowany i jakie jest uzasadnienie?

alex
źródło

Odpowiedzi:

234

Zostało to wspomniane na stronie instrukcji unset w 2009 roku :

unset()robi dokładnie to, co mówi jego nazwa - odznacz zmienną. Nie wymusza natychmiastowego zwolnienia pamięci. Śmieciarka PHP zrobi to, gdy zobaczy pasowanie - z zamiarem, jak tylko te cykle procesora i tak nie będą potrzebne, lub tak późno, jak wcześniej skryptowi zabraknie pamięci, cokolwiek nastąpi wcześniej.

Jeśli to robisz $whatever = null;, przepisujesz dane zmiennej. Być może pamięć zostanie zwolniona / zmniejszona szybciej, ale może ukraść cykle procesora z kodu, który naprawdę ich potrzebuje wcześniej, co powoduje dłuższy całkowity czas wykonania.

(Od 2013 r. unsetTa strona podręcznika nie zawiera już tej sekcji)

Zauważ, że do php5.3, jeśli masz dwa obiekty w odwołaniu cyklicznym , takim jak relacja rodzic-dziecko, wywołanie unset () na obiekcie nadrzędnym nie zwolni pamięci używanej dla odwołania rodzica w obiekcie potomnym. (Pamięć nie zostanie również zwolniona, gdy obiekt nadrzędny zostanie wyrzucony do pamięci). ( Błąd 33595 )


Pytanie „ różnica między unset a = null ” wyszczególnia niektóre różnice:


unset($a)usuwa również $az tablicy symboli; na przykład:

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

Wyjścia:

Notice: Undefined variable: a in xxx
NULL

Ale kiedy $a = nulljest używany:

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

NULL

Wygląda na $a = nullto, że jest nieco szybszy niż jego unset()odpowiednik: aktualizacja wpisu tablicy symboli wydaje się szybsza niż jego usunięcie.


  • przy próbie użycia nieistniejącej unsetzmiennej ( ) zostanie wyzwolony błąd, a wartość wyrażenia zmiennej będzie zerowa. (Bo co jeszcze powinien zrobić PHP? Każde wyrażenie musi mieć jakąś wartość.)
  • Zmienna z przypisanym null jest jednak nadal całkowicie normalną zmienną.
VonC
źródło
18
Zauważ, że jeśli $whateverwskazuje na obiekt, $whatever = nullzastępuje wskaźnik, a nie sam obiekt, więc działa on w zasadzie tak samo jak unset().
Gras Double
1
@VonC: nieokreślony cytat na php.net, o którym mówisz, już nie istnieje.
Jürgen Thelen
@ JürgenThelen prawda, ale treść tej starej odpowiedzi nadal wydaje się aktualna, nie?
VonC
1
@VonC: Zdecydowanie. Po prostu nie jestem pewien, czy „cykle procesora nie są potrzebne” i „zanim ... brak pamięci” powoduje wyrzucanie elementów bezużytecznych. Zobacz stackoverflow.com/q/20230626/693207 . Może możesz rzucić trochę światła?
Jürgen Thelen
1
@Omar Zredagowałem odpowiedź: Nieuzbrojona strona podręcznika użytkownika z 2009 r. (Mam link do wersji 2009) zawiera sekcję, która nie jest już obecna w bieżącej wersji tej samej strony.
VonC
48

unsetnie jest właściwie funkcją, ale konstrukcją języka . To nie jest więcej wywołanie funkcji niż a returnlub an include.

Oprócz problemów z wydajnością, użycie unsetsprawia, że intencje twojego kodu są znacznie wyraźniejsze.

Alex Barrett
źródło
Dlatego zawsze ich używałem, osobiście uważałem, że wyglądają lepiej niż $ var = null. Nawiasem mówiąc, zawsze używałem NULL full cap ... ale teraz nie wiem dlaczego?
alex
1
@VonC: Tak, doszedłem do tego, ale dlaczego możesz używać małych liter prawda, fałsz i zero?
alex
3
@alex, możesz to zrobić z unset. Na przykład „$ test = 4; (unset) $ test;” - dziwne, ale prawdziwe, i zwraca wartość $ test przed rozbrojeniem. Niezależnie od tego instrukcja PHP potwierdza, że ​​jest to konstrukcja językowa.
thomasrutter
5
@alex: PSR-2 wymaga małych liter dla wszystkich słów kluczowych.
Tgr
2
@alex - słowa kluczowe PHP nie rozróżniają wielkości liter; na przykład możesz przeliterować unsetjako UnSeT. Społeczność zdecydowała się na małe litery ze względu na styl, ale inne obudowy nadal działają.
Mark Reed
35

Wykonując unset () na zmiennej, zasadniczo zaznaczyłeś zmienną dla „odśmiecania” (PHP tak naprawdę jej nie ma, ale na przykład dla dobra), więc pamięć nie jest natychmiast dostępna. Zmienna nie mieści już danych, ale stos pozostaje w większym rozmiarze. Wykonanie metody zerowej powoduje spadek danych i prawie natychmiast zmniejsza pamięć stosu.

To było z własnego doświadczenia i innych. Zobacz komentarze funkcji unset () tutaj .

Osobiście używam unset () między iteracjami w pętli, aby nie musiałem opóźniać wielkości stosu. Dane zniknęły, ale pozostał ślad. Przy następnej iteracji pamięć jest już pobierana przez php, a zatem szybciej inicjuje następną zmienną.

William Holroyd
źródło
15
Ustawienie wartości NULL może być korzystne, jeśli pamięć wymagana do przechowywania wartości NULL jest mniejsza niż wymagana do przechowywania dowolnej wartości, którą poprzednio trzymała. Na przykład długi ciąg. Jeśli łańcuch nie był stały, a jego liczba referencyjna spadła do zera, pamięć ta powinna zostać zwolniona. Unset jest czystszy - nie zachowuje już referencji. Musisz czekać na wyrzucanie elementów bezużytecznych, ale można bezpiecznie traktować je jako brak pamięci, ponieważ niski poziom pamięci spowoduje wyrzucanie elementów bezużytecznych.
thomasrutter
nie możemy używać obu? równa zero, a następnie rozbrojony?
Nabeel Khan
2
@NabeelKhan Sugerowałbym użycie unset () wewnątrz pętli, a następnie nullify po wyjściu z pętli. W przeciwnym razie wykonanie obu w pętli ma wpływ na wydajność. Jeśli nie używasz pętli, po prostu nullify, ponieważ już działa logika unset () za scenami.
William Holroyd,
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";
?>

Wydaje się, że „= null” jest szybszy.

Wyniki PHP 5.4:

  • zajęło 0,88389301300049 sekund
  • zajęło 2.1757180690765 sekund

Wyniki PHP 5.3:

  • zajęło 1.7235369682312 sekund
  • zajęło 2.9490959644318 sekund

Wyniki PHP 5.2:

  • zajęło 3.0069220066071 sekund
  • zajęło 4.7002630233765 sekund

Wyniki PHP 5.1:

  • zajęło 2.6272349357605 sekund
  • zajęło 5.0403649806976 sekund

Z PHP 5.0 i 4.4 wszystko zaczyna wyglądać inaczej.

5.0:

  • zajęło 10.038941144943 sekund
  • zajęło 7.0874409675598 sekund

4.4:

  • zajęło 7,5352551937103 sekund
  • zajęło 6,6245851516724 sekund

Pamiętaj, że microtime (prawda) nie działa w PHP 4.4, więc musiałem użyć przykładu microtime_float podanego w php.net/microtime / Przykład # 1.


źródło
7
Myślę, że twój test jest wadliwy. Pierwsza pętla to proste ponowne przypisanie, a druga pętla niszczy i odtwarza ten sam symbol. Jeśli test zostanie powtórzony, tablica unsetjest szybsza. Mam test, który później sprawdza istnienie w unsetsprawie. W tym teście ustawienie to nulljest nieznacznie szybsze. Test: pastebin.com/fUe57C51
Knyri
4
@ansur, zawsze zadzwoń gc_collect_cyclesprzed uruchomieniem stopera, aby uzyskać dokładniejsze wyniki.
Pacerier
@ knyri, czy możesz podać link do tego?
Nabeel Khan
@NabeelKhan Nie mam już wyników tego testu; ale w moim poprzednim komentarzu jest link do kodu testowego.
Knyri
19

To robi różnicę w przypadku elementów tablicy.

Rozważ ten przykład

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

Tutaj kluczowy „test” nadal istnieje. Jednak w tym przykładzie

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

klucz już nie istnieje.

Auris
źródło
18

Działa w inny sposób dla zmiennych kopiowanych przez odniesienie:

$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
RiaD
źródło
5
Kodowałem php już kilka lat i nigdy nie widziałem „&” na temat odniesienia do oryginalnego var. Dzięki + 1 :)
Chris
1
$ a = 78; $ b = $ a; rozbrojony ($ a); var_dump ($ b); // 78; var_dump ($ a); // Niezdefiniowana zmienna: a
zloctb
13

Jeśli chodzi o obiekty, szczególnie w scenariuszu z leniwym ładowaniem, należy wziąć pod uwagę, że moduł wyrzucania elementów bezużytecznych działa w bezczynnych cyklach procesora, więc zakładając, że masz kłopoty, gdy ładuje się wiele obiektów, mała kara czasowa rozwiąże zwolnienie pamięci.

Użyj time_nanosleep, aby umożliwić GC gromadzenie pamięci. Pożądane jest ustawienie zmiennej na null.

Testowane na serwerze produkcyjnym, początkowo zadanie zużywało 50 MB, a następnie zostało zatrzymane. Po użyciu nanosnu 14 MB było stałym zużyciem pamięci.

Należy powiedzieć, że zależy to od zachowania GC, które może się zmieniać z wersji PHP na wersję. Ale działa dobrze na PHP 5.3.

na przykład. ta próbka (kod pochodzi z kanału 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);
    ...
OSP
źródło
3

Nadal mam co do tego wątpliwości, ale wypróbowałem to w swoim skrypcie i używam xdebug, aby wiedzieć, jak wpłynie to na użycie pamięci przez moją aplikację. Skrypt jest ustawiony dla mojej funkcji w następujący sposób:

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

I dodaję nieustawiony tuż przed returnkodem i daje mi: 160200, a następnie próbuję go zmienić $sql = NULLi daje mi: 160224 :)

Ale jest coś wyjątkowego w tym porównaniu, gdy nie używam unset () lub NULL, xdebug daje mi 160144 jako użycie pamięci

Tak więc myślę, że podanie wiersza do użycia unset () lub NULL doda proces do twojej aplikacji i lepiej pozostanie pochodzenie z twoim kodem i zmniejszenie zmiennej, której używasz tak efektywnie, jak to możliwe.

Popraw mnie, jeśli się mylę, dzięki

Anggie Aziz
źródło
Myślę, że gdy zwracasz element $ data [0], cała tablica jest przywoływana / ale to tylko hipoteza. Spróbuj skopiować $ data [0] do zmiennej lokalnej, ustaw tablicę na null i zwróć zmienną lokalną. Dobre tło znajduje się tutaj tuxradar.com/practicalphp/18/1/11 i ofc . php.net/manual/en/features.gc.php
OSP
2

Stworzyłem nowy test wydajności dla unseti =null, ponieważ, jak wspomniano w komentarzach, tutaj napisany jest błąd (odtwarzanie elementów). Użyłem tablic, jak widzisz, nie miało to teraz znaczenia.

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

Ale mogę go przetestować tylko na serwerze PHP 5.5.9, tutaj wyniki: - zajęło 4,4571571350098 sekund - zajęło 4,4425978660583 sekund

Wolę unsetze względu na czytelność.

Michael B.
źródło
2

PHP 7 już pracuje nad takimi problemami z zarządzaniem pamięcią i jej ograniczeniem do minimalnego zużycia.

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

zajęło 0.16778993606567 sekund zajęło 0.16630101203918 sekund

Swapnil
źródło
1

unsetkod, jeśli nie zwolnienie natychmiastowej pamięci, jest nadal bardzo pomocne i dobrą praktyką byłoby to robić za każdym razem, gdy przekazujemy kroki kodu przed wyjściem z metody. zwróć uwagę, że nie chodzi o uwolnienie natychmiastowej pamięci. pamięć bezpośrednia jest przeznaczona na procesor, co z pamięcią dodatkową, którą jest RAM.

i dotyczy to również zapobiegania wyciekom pamięci.

patrz ten link http://www.hackingwithphp.com/18/1/11/be-wary-of-garbage-collection-part-2

Od dłuższego czasu używam unset.

lepsza praktyka taka jak w kodzie, aby natychmiast rozbroić wszystkie zmienne, które były już używane jako tablica.

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

i just unset($data);uwolnić wszystkie zmienne użycie.

proszę zobaczyć pokrewny temat do rozbrojenia

Jak ważne jest rozbrajanie zmiennych w PHP?

[pluskwa]

zero8
źródło
1

Dla przypomnienia, z wyłączeniem czasu, który zajmuje:

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

Powraca

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

Wniosek, zarówno zerowa, jak i nieustawiona wolna pamięć, zgodnie z oczekiwaniami (nie tylko na końcu wykonywania). Ponadto ponowne przypisanie zmiennej zachowuje wartość dwukrotnie w pewnym momencie (520216 w porównaniu z 438352)

magallanes
źródło