2009-07-17 14 views
15

W nadziei, że staram się uniknąć przyszłych wycieków pamięci w programach php (moduły drupal itp.), Mam do czynienia z prostymi skryptami php, które przeciekają pamięć.Dlaczego ten prosty skrypt php powoduje wyciek pamięci?

Czy ekspert php może mi pomóc znaleźć informacje na temat tego skryptu, które powoduje, że zużycie pamięci stale rośnie?

Spróbuj uruchomić samodzielnie, zmieniając różne parametry. Wyniki są interesujące. Oto ona:

<?php 

function memstat() { 
    print "current memory usage: ". memory_get_usage() . "\n"; 
} 

function waste_lots_of_memory($iters) { 
    $i = 0; 
    $object = new StdClass; 
    for (;$i < $iters; $i++) { 
    $object->{"member_" . $i} = array("blah blah blha" => 12345); 
    $object->{"membersonly_" . $i} = new StdClass; 
    $object->{"onlymember"} = array("blah blah blha" => 12345); 
    } 
    unset($object); 
} 

function waste_a_little_less_memory($iters) { 
    $i = 0; 
    $object = new StdClass; 
    for (;$i < $iters; $i++) { 

    $object->{"member_" . $i} = array("blah blah blha" => 12345); 
    $object->{"membersonly_" . $i} = new StdClass; 
    $object->{"onlymember"} = array("blah blah blha" => 12345); 

    unset($object->{"membersonly_". $i}); 
    unset($object->{"member_" . $i}); 
    unset($object->{"onlymember"}); 

    } 
    unset($object); 
} 

memstat(); 

waste_a_little_less_memory(1000000); 

memstat(); 

waste_lots_of_memory(10000); 

memstat(); 

Dla mnie wyjście jest:

current memory usage: 73308 
current memory usage: 74996 
current memory usage: 506676 

[edytowane na wyłączony większej liczby członków Object]

+0

Chciałbym spróbować usunąć linie w pętli for na raz, aby wyizolować problem. –

Odpowiedz

34

unset() nie zwalnia pamięci używanej przez zmienną. Pamięć zostaje zwolniona, gdy "garbage collector" (w cudzysłowie, ponieważ PHP nie miał prawdziwego garbage collectora przed wersją 5.3.0, tylko rutynowa pamięć bez pamięci, która działała głównie na prymitywach) uważa, że ​​pasuje.

Ponadto, technicznie, nie powinieneś dzwonić pod numer unset(), ponieważ zmienna $object jest ograniczona do zakresu twojej funkcji.

Oto skrypt, który pokazuje różnicę. Zmodyfikowałem twoją funkcję memstat(), aby pokazać różnicę w pamięci od ostatniego połączenia.

<?php 
function memdiff() { 
    static $int = null; 

    $current = memory_get_usage(); 

    if ($int === null) { 
     $int = $current; 
    } else { 
     print ($current - $int) . "\n"; 
     $int = $current; 
    } 
} 

function object_no_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 
     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 
    } 
} 

function object_parent_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 
     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 
    } 

    unset ($object); 
} 

function object_item_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 

     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 

     unset ($object->{"membersonly_" . $i}); 
     unset ($object->{"member_" . $i}); 
     unset ($object->{"onlymember"}); 
    } 
    unset ($object); 
} 

function array_no_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 
    } 
} 

function array_parent_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 
    } 
    unset ($object); 
} 

function array_item_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 

     unset ($object["membersonly_" . $i]); 
     unset ($object["member_" . $i]); 
     unset ($object["onlymember"]); 
    } 
    unset ($object); 
} 

$iterations = 100000; 

memdiff(); // Get initial memory usage 

object_item_unset ($iterations); 
memdiff(); 

object_parent_unset ($iterations); 
memdiff(); 

object_no_unset ($iterations); 
memdiff(); 

array_item_unset ($iterations); 
memdiff(); 

array_parent_unset ($iterations); 
memdiff(); 

array_no_unset ($iterations); 
memdiff(); 
?> 

Jeśli używasz przedmiotów, upewnij klasy implementuje __unset() w celu umożliwienia unset() właściwie jasne zasobów. Staraj się unikać w jak największym stopniu korzystania z klas o zmiennej strukturze, takich jak stdClass lub przypisywania wartości do elementów, które nie znajdują się w szablonie klasy, ponieważ pamięć przypisana do tych, które są zazwyczaj niepoprawnie wyczyszczone.

PHP 5.3.0 i nowsze ma lepszy garbage collector, ale jest domyślnie wyłączone. Aby go włączyć, musisz raz zadzwonić pod numer gc_enable().

+0

@mjgoins: Edytowałem mój post z dodatkowymi informacjami. Domyślny moduł zbierający działa najlepiej na typach pierwotnych. Gdy tylko zaczniesz wprowadzać zasoby i obiekty do miksu, zaczyna się to nie udać. –

+0

Tak więc odpowiedź brzmi aż do php 5.3, php * nie może * uruchomić arbitralnie długiego zadania w stałej pamięci. Nawet moja funkcja, która wycieka z mniejszej ilości pamięci, powoli się skończy, chociaż przy dużej alokacji pamięci zajęłoby to wiele dni. – mjgoins

+0

@mjgoins: uruchamiam wiele zadań, które mogą zająć od jednej minuty do wielu godzin. Sztuką jest unikanie obiektów (lub próba ich ponownego użycia, jeśli musisz) i trzymanie się prymitywów (w twoim przykładzie możesz łatwo korzystać z tablic). –

2

Moje rozumienie memory_get_usage() jest to, że produkcja może zależeć od szeroki zakres czynników systemu operacyjnego i wersji.

Co ważniejsze, rozbrojenie zmiennej nie zwalnia natychmiast jej pamięci, zwalnia ją z procesu i oddaje z powrotem do systemu operacyjnego (ponownie, charakterystyka tej operacji zależy od systemu operacyjnego).

W skrócie, prawdopodobnie potrzebujesz bardziej skomplikowanej konfiguracji, aby spojrzeć na wycieki pamięci.

0

Nie jestem pewien co do dokładnego działania tego w PHP, ale w niektórych innych językach obiekt zawierający inne obiekty, gdy ustawiony jest na wartość null, nie ustawia z natury innych obiektów na wartość null. Kończy odwołanie do tych obiektów, ale ponieważ PHP nie posiada "garbage collection" w sensie Java, sub-obiekty istnieją w pamięci, dopóki nie zostaną usunięte pojedynczo.

+0

Jeśli ktoś się z tym spotka, proces zwalniania pamięci, gdy obiekt (lub inna wartość) nie ma żadnych odniesień, nie jest w PHP uważany za "garbage collector", ponieważ nie jest oddzielny element funkcjonalności, ale to nie znaczy, że tak się nie dzieje. Odniesienia do dowolnej zmiennej są zliczane, a 'unset()' etc zmniejsza tę liczbę o jeden; jeśli liczba zostanie zredukowana do zera, pamięć zostanie natychmiast zwolniona w menedżerze pamięci PHP, gotowa do użycia przez nową zmienną. – IMSoP

3

memory_get_usage informuje, ile pamięci php przydzielono z os. Nie musi to być wielkość wszystkich używanych zmiennych. Jeśli php ma maksymalne wykorzystanie pamięci, może zdecydować, że nie zwróci niewykorzystanej ilości pamięci od razu. W twoim przykładzie funkcja zeruje nieużywane zmienne w czasie. Więc maksymalne wykorzystanie jest względnie małe. waste_lots_of_memory tworzy wiele zmiennych (= dużo używanej pamięci) przed zwolnieniem. Więc maksymalne wykorzystanie jest znacznie większe.

22

memory_get_usage()Zwraca ilość pamięci w bajtach, który jest obecnie przypisane do skryptu PHP.

To ilość pamięci przydzielona do procesu przez system operacyjny, nie ilość pamięć używana przez przypisane zmienne. PHP nie zawsze zwalnia pamięć z powrotem do systemu operacyjnego - ale pamięć ta może być ponownie wykorzystana po przydzieleniu nowych zmiennych.

Demonstrowanie tego jest proste. Zmień koniec skryptu do:

memstat(); 
waste_lots_of_memory(10000); 
memstat(); 
waste_lots_of_memory(10000); 
memstat(); 

Teraz, jeśli jesteś poprawne, a PHP jest faktycznie wyciek pamięci, powinieneś zobaczyć useage pamięci dwukrotnie rośnie. Jednak tutaj rzeczywisty wynik:

current memory usage: 88272 
current memory usage: 955792 
current memory usage: 955808 

Wynika to pamięć „uwolniony” zaraz po wywołaniu waste_lots_of_memory() jest wykorzystywana ponownie przez drugie wywołania.

W ciągu 5 lat pracy z PHP napisałem skrypty, które przetwarzały miliony obiektów i gigabajtów danych w ciągu kilku godzin oraz skrypty uruchamiane przez wiele miesięcy. Zarządzanie pamięcią w PHP nie jest wspaniałe, ale nie jest tak źle, jak się wydaje.

+0

To jest poprawna odpowiedź na to pytanie i zasługuje na dużo więcej awansów niż otrzymała. – IMSoP

0

Funkcja memory_get_usage() nie zwraca natychmiastowego użycia pamięci, ale pamięć jest przechowywana w celu uruchomienia procesu. w przypadku ogromnej tablicy unset ($ array_a) nie wyda pamięci, ale zużywają więcej według memory_get_usage() w moim systemie ...

$array_a="(SOME big array)"; 
$array_b=""; 
//copy array_a to array_b 
for($line=0; $line<100;$line++){ 
$array_b[$line]=$array_a[$line]; 
} 

unset($array_a); //having this shows actually a bigger consume 
print_r($array_b); 

echo memory_get_usage();