Jak określić ślad pamięci (rozmiar) zmiennej?

102

Czy w PHP (lub rozszerzeniu PHP) jest funkcja sprawdzająca, ile pamięci zużywa dana zmienna? sizeofpo prostu podaje liczbę elementów / właściwości.

memory_get_usagepomaga w tym, że daje mi rozmiar pamięci używanej przez cały skrypt. Czy istnieje sposób na zrobienie tego dla pojedynczej zmiennej?

Zauważ, że jest to na komputerze deweloperskim, więc ładowanie rozszerzeń lub narzędzi do debugowania jest możliwe.

Piskvor opuścił budynek
źródło
Edytowano - minęło 5 lat, a niektóre problemy wciąż pozostają nierozwiązane :(
Piskvor opuścił budynek

Odpowiedzi:

46

Prawdopodobnie potrzebujesz profilera pamięci. Zebrałem informacje na ten temat, ale skopiowałem kilka ważnych rzeczy, które mogą ci pomóc.

Jak zapewne wiesz, Xdebug porzucił obsługę profilowania pamięci od wersji 2. *. Wyszukaj ciąg „usunięte funkcje” tutaj: http://www.xdebug.org/updates.php

Usunięte funkcje

Usunięto obsługę profilowania pamięci, ponieważ nie działało to poprawnie.

Inne opcje profilera

php-memory-profiler

https://github.com/arnaud-lb/php-memory-profiler . Oto, co zrobiłem na moim serwerze Ubuntu, aby to włączyć:

sudo apt-get install libjudy-dev libjudydebian1
sudo pecl install memprof
echo "extension=memprof.so" > /etc/php5/mods-available/memprof.ini
sudo php5enmod memprof
service apache2 restart

A potem w moim kodzie:

<?php
memprof_enable();
// do your stuff
memprof_dump_callgrind(fopen("/tmp/callgrind.out", "w"));

Na koniec otwórz callgrind.outplik za pomocą KCachegrind

Korzystanie z Google gperftools (zalecane!)

Przede wszystkim zainstaluj gperftools Google , pobierając najnowszy pakiet tutaj: https://code.google.com/p/gperftools/

Wtedy jak zawsze:

sudo apt-get update
sudo apt-get install libunwind-dev -y
./configure
make
make install

Teraz w twoim kodzie:

memprof_enable();

// do your magic

memprof_dump_pprof(fopen("/tmp/profile.heap", "w"));

Następnie otwórz terminal i uruchom:

pprof --web /tmp/profile.heap

pprof utworzy nowe okno w istniejącej sesji przeglądarki, jak pokazano poniżej:

Profilowanie pamięci PHP za pomocą memprof i gperftools

Xhprof + Xhgui (moim zdaniem najlepiej profiluje zarówno procesor jak i pamięć)

Dzięki Xhprof i Xhgui możesz również profilować użycie procesora lub tylko użycie pamięci, jeśli jest to obecnie Twój problem. To bardzo kompletne rozwiązanie, daje pełną kontrolę, a logi można zapisywać zarówno w mongo, jak iw systemie plików.

Więcej informacji można znaleźć tutaj .

Czarny ogień

Blackfire to profiler PHP stworzony przez SensioLabs, gości z Symfony2 https://blackfire.io/

Jeśli używasz Puphpet do skonfigurowania maszyny wirtualnej, z przyjemnością dowiesz się, że jest obsługiwana ;-)

Xdebug i śledzenie użycia pamięci

XDEBUG2 to rozszerzenie dla PHP. Xdebug umożliwia rejestrowanie wszystkich wywołań funkcji, w tym parametrów i zwracanie wartości do pliku w różnych formatach. Istnieją trzy formaty wyjściowe. Jeden jest pomyślany jako czytelny dla człowieka ślad, inny jest bardziej odpowiedni dla programów komputerowych, ponieważ jest łatwiejszy do przeanalizowania, a ostatni używa HTML do formatowania śladu. Możesz przełączać się między dwoma różnymi formatami za pomocą tego ustawienia. Przykład byłby dostępny tutaj

dla p

forp prosty, nieinwazyjny, zorientowany na produkcję, profiler PHP. Oto niektóre funkcje:

  • pomiar czasu i przydzielonej pamięci dla każdej funkcji

  • użycie procesora

  • plik i numer linii wywołania funkcji

  • dane wyjściowe w formacie Google Trace Event

  • podpis funkcji

  • grupowanie funkcji

  • aliasy funkcji (przydatne w przypadku funkcji anonimowych)

DBG

DBG to w pełni funkcjonalny debugger php, interaktywne narzędzie, które pomaga debugować skrypty php. Działa na produkcyjnym i / lub deweloperskim serwerze WEB i umożliwia debugowanie skryptów lokalnie lub zdalnie, z IDE lub konsoli, a jego funkcje to:

  • Zdalne i lokalne debugowanie

  • Jawna i niejawna aktywacja

  • Stos wywołań, w tym wywołania funkcji, dynamiczne i statyczne wywołania metod, wraz z ich parametrami

  • Nawigacja po stosie wywołań z możliwością oceny zmiennych w odpowiednich (zagnieżdżonych) miejscach

  • Wejdź / Wyjdź / Przejdź nad / Uruchom do funkcji kursora

  • Warunkowe punkty przerwania

  • Globalne punkty przerwania

  • Rejestrowanie błędów i ostrzeżeń

  • Wiele jednoczesnych sesji do równoległego debugowania

  • Obsługa frontonów GUI i CLI

  • Obsługiwane sieci IPv6 i IPv4

  • Wszystkie dane przesyłane przez debugger mogą być opcjonalnie chronione za pomocą SSL

Vineet1982
źródło
2
To jest dokładnie ta informacja, której szukałem, dziękuję.
Piskvor opuścił budynek
93

Nie ma bezpośredniego sposobu na uzyskanie wykorzystania pamięci dla pojedynczej zmiennej, ale jak zasugerował Gordon, możesz użyć memory_get_usage. To zwróci całkowitą ilość przydzielonej pamięci, więc możesz użyć obejścia i zmierzyć użycie przed i po, aby uzyskać użycie jednej zmiennej. To trochę hakerskie, ale powinno działać.

$start_memory = memory_get_usage();
$foo = "Some variable";
echo memory_get_usage() - $start_memory;

Zauważ, że nie jest to w żaden sposób niezawodna metoda, nie możesz być pewien, że nic innego nie dotyka pamięci podczas przypisywania zmiennej, więc powinno to być używane tylko jako przybliżenie.

W rzeczywistości można to przekształcić w funkcję, tworząc kopię zmiennej wewnątrz funkcji i mierząc wykorzystaną pamięć. Nie testowałem tego, ale w zasadzie nie widzę w tym nic złego:

function sizeofvar($var) {
    $start_memory = memory_get_usage();
    $tmp = unserialize(serialize($var));
    return memory_get_usage() - $start_memory;
}
Tatu Ulmanen
źródło
14
$tmp = $varutworzy płytką kopię. To nie przydzieli większej ilości pamięci, dopóki $ tmp nie zostanie zmodyfikowane.
Gordon,
@Gordon, masz rację, trochę przeoczyłem ten punkt. Ponieważ nie mogę znaleźć właściwego sposobu modyfikowania zmiennej bez zmiany jej typu lub rozmiaru, zostawię to. Może ktoś
wpadnie
7
co powiesz na $tmp = unserialize(serialize($var)); To połączyłoby powyższe podejście Aistiny.
Gordon
3
również, ponieważ $varjest już płytką kopią lub odniesieniem do tego, co zostało przekazane do funkcji, nie potrzebujesz $tmp, ale możesz ponownie przypisać $var. Spowoduje to zapisanie wewnętrznego odniesienia z $tmpdo $var.
Gordon
Nie jest jakiś bardziej elegancki sposób dereference $tmpod $var?
Tomáš Zato - Przywróć Monikę
24

Nie, nie ma. Ale możesz serialize($var)i sprawdzić strlenwynik dla przybliżenia.

Aistina
źródło
Jest to znacznie lepsze podejście, ponieważ pozwala uniknąć całej sprawy z GC.
Gleno,
12
To straszne przybliżenie. Każda pozycja w tablicy w PHP ma ~ 80 bajtów, ale strlen(serialize(array(1,2,3)))ma 30.
gsnedders
2
@Aistina, -1. mierzysz niewłaściwą rzecz. Zmienna i zmienna serializowana to dwie zupełnie różne rzeczy i dadzą zupełnie inne wyniki.
Pacerier,
1
Nie tylko to, ale całkowicie zawiedzie w przypadku niektórych struktur danych, których nie można serializować, np. Odwołań cyklicznych.
duskwuff
20

W odpowiedzi do Tatu Ulmanensa odpowiedz:

Należy zauważyć, że $start_memorysam zajmie pamięć ( PHP_INT_SIZE * 8).

Zatem cała funkcja powinna wyglądać następująco:

function sizeofvar($var) {
    $start_memory = memory_get_usage();
    $var = unserialize(serialize($var));
    return memory_get_usage() - $start_memory - PHP_INT_SIZE * 8;
}

Przepraszam, że dodam to jako dodatkową odpowiedź, ale nie mogę jeszcze skomentować odpowiedzi.

Aktualizacja: * 8 nie jest zdefiniowana. Może to najwyraźniej zależeć od wersji php i prawdopodobnie od 64/32 bitów.

ust
źródło
4
Czy możesz wyjaśnić dlaczego * 8? Dzięki!
sierrasdetandil
@sierrasdetandil Wygląda na to, że $ start_memory nie zajmuje tylko PHP_INT_SIZEbajtów, ale PHP_INT_SIZE*8. Możesz spróbować, wywołując tę ​​funkcję, powinna ona zwrócić 0:function sizeofvar() { $start_memory = memory_get_usage(); return memory_get_usage() - $start_memory - PHP_INT_SIZE*8; }
para
8nie wydaje się stały. Po twojej funkcji komentowania w moim systemie deweloperskim (PHP 5.6.19), zwraca -16. Co ciekawe, php -awywołanie dwóch wierszy funkcji z funkcji daje różne wartości.
Paul DelRe,
@PaulDelRe tak, prawdopodobnie jest to zależne od wersji / 64bit tego rodzaju rzeczy.
para
teraz błąd krytyczny występuje przy wywołaniu unserialize (). To nie jest pomoc! Jeśli zmienna jest tak duża, że ​​zabraknie jej pamięci, wywołanie funkcji na tej zmiennej spowoduje zużycie nawet WIĘCEJ pamięci. :(
john ktejik
4

Widzieć:

Pamiętaj jednak, że nie da ci to wykorzystania pamięci przez określoną zmienną. Ale możesz wywołać te funkcje przed i po przypisaniu zmiennej, a następnie porównać wartości. To powinno dać ci wyobrażenie o używanej pamięci.

Możesz także rzucić okiem na rozszerzenie PECL Memtrack , chociaż dokumentacji trochę brakuje, jeśli nie powiedzieć, praktycznie nie istnieje.

Gordon
źródło
Tak. Można go użyć pośrednio, aby odpowiedzieć na pytanie.
Notinlist
3

Możesz zdecydować się na obliczanie różnicy pamięci dla wartości zwracanej przez wywołanie zwrotne. To bardziej eleganckie rozwiązanie dostępne w PHP 5.3+.

function calculateFootprint($callback) {
    $startMemory = memory_get_usage();
    $result = call_user_func($callback);
    return memory_get_usage() - $startMemory;
}

$memoryFootprint = calculateFootprint(
    function() {
        return range(1, 1000000);
    }
);

echo ($memoryFootprint / (1024 * 1024)) . ' MB' . PHP_EOL;
Liviu Bundă
źródło
3

Nie można retrospektywnie obliczyć dokładnego śladu zmiennej, ponieważ dwie zmienne mogą dzielić to samo przydzielone miejsce w pamięci

Spróbujmy podzielić pamięć między dwiema tablicami, widzimy, że przydzielenie drugiej tablicy kosztuje połowę pamięci pierwszej. Kiedy rozbroimy pierwszą, prawie cała pamięć jest nadal zajęta przez drugą.

echo memory_get_usage()."\n"; // <-- 433200
$c=range(1,100);
echo memory_get_usage()."\n"; // <-- 444348 (+11148)
$d=array_slice($c, 1);
echo memory_get_usage()."\n"; // <-- 451040 (+6692)
unset($c);
echo memory_get_usage()."\n"; // <-- 444232 (-6808)
unset($d);
echo memory_get_usage()."\n"; // <-- 433200 (-11032)

Nie możemy więc wnioskować, że druga tablica zużywa połowę pamięci, ponieważ staje się fałszywa, gdy usuwamy pierwszą.

Aby uzyskać pełny obraz tego, jak alokowana jest pamięć w PHP i do czego służy, sugeruję przeczytanie następującego artykułu: Jak duże są naprawdę tablice (i wartości) PHP? (Podpowiedź: DUŻA!)

W sekcji Podstawy liczenia odniesień w dokumentacji PHP znajduje się również wiele informacji o wykorzystaniu pamięci, a odwołania liczą się do segmentu danych współdzielonych.

Przedstawione tutaj różne rozwiązania są dobre do przybliżeń, ale żadne nie poradzi sobie z subtelnym zarządzaniem pamięcią PHP.

  1. obliczenie nowo przydzielonej powierzchni

Jeśli chcesz nowo przydzieloną przestrzeń po przydziale, musisz użyć jej memory_get_usage()przed i po przydziale, ponieważ używanie jej z kopią daje błędny obraz rzeczywistości.

// open output buffer
echo "Result: ";
// call every function once
range(1,1); memory_get_usage();

echo memory_get_usage()."\n";
$c=range(1,100);
echo memory_get_usage()."\n";

Pamiętaj, że jeśli chcesz zapisać wynik pierwszej memory_get_usage(), zmienna musi już istnieć wcześniej i memory_get_usage()musi być nazwana poprzednio inną, a także każdą inną funkcją.

Jeśli chcesz wywołać echo jak w powyższym przykładzie, twój bufor wyjściowy musi być już otwarty, aby uniknąć pamięci rozliczeniowej potrzebnej do otwarcia bufora wyjściowego.

  1. obliczenie wymaganej przestrzeni

Jeśli chcesz polegać na funkcji obliczającej wymaganą przestrzeń do przechowywania kopii zmiennej, poniższy kod zajmuje się różnymi optymalizacjami:

<?php
function getMemorySize($value) {
    // existing variable with integer value so that the next line
    // does not add memory consumption when initiating $start variable
    $start=1;
    $start=memory_get_usage();
    // json functions return less bytes consumptions than serialize
    $tmp=json_decode(json_encode($value));
    return memory_get_usage() - $start;
}

// open the output buffer, and calls the function one first time
echo ".\n";
getMemorySize(NULL);

// test inside a function in order to not care about memory used
// by the addition of the variable name to the $_GLOBAL array
function test() {
    // call the function name once 
    range(1,1);

    // we will compare the two values (see comment above about initialization of $start)
    $start=1;
    $start=memory_get_usage();
    $c=range(1,100);
    echo memory_get_usage()-$start."\n";
    echo getMemorySize($c)."\n";
}
test();

// same result, this works fine.
// 11044
// 11044

Zauważ, że rozmiar nazwy zmiennej ma znaczenie w przydzielonej pamięci.

  1. Sprawdź swój kod !!

Zmienna ma podstawowy rozmiar zdefiniowany przez wewnętrzną strukturę C używaną w kodzie źródłowym PHP. Wielkość ta nie zmienia się w przypadku liczb. W przypadku ciągów dodawałby długość ciągu.

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

Jeśli nie weźmiemy pod uwagę inicjalizacji nazwy zmiennej, wiemy już, ile używa zmienna (w przypadku liczb i łańcuchów):

44 bajty w przypadku liczb

+ 24 bajty w przypadku łańcuchów

+ długość ciągu (łącznie z ostatnim znakiem NUL)

(te liczby mogą się zmieniać w zależności od wersji PHP)

Musisz zaokrąglić do wielokrotności 4 bajtów ze względu na wyrównanie pamięci. Jeśli zmienna znajduje się w przestrzeni globalnej (nie wewnątrz funkcji), przydzieli również 64 dodatkowe bajty.

Więc jeśli chcesz użyć jednego z kodów na tej stronie, musisz sprawdzić, czy wynik za pomocą prostych przypadków testowych (ciągów lub liczb) jest zgodny z tymi danymi, biorąc pod uwagę wszystkie wskazania w tym poście (tablica $ _GLOBAL, pierwsze wywołanie funkcji, bufor wyjściowy, ...)

Adam
źródło
1
... i to zanim jeszcze dostać się do wewnętrznych części zvalue, is_refi kopiowanie przy zapisie. Dziękuję Ci.
Piskvor opuścił budynek
1
Dzięki tobie przegapiłem tę stronę w podręczniku PHP. Dodałem link, aby uzupełnić moją odpowiedź (ale myślę, że już to przeczytałeś).
Adam
2

Miałem podobny problem i rozwiązaniem, którego użyłem, było zapisanie zmiennej do pliku, a następnie uruchomienie na nim filesize (). Mniej więcej tak (nieprzetestowany kod):

function getVariableSize ( $foo ) 
{
    $tmpfile = "temp-" . microtime(true) . ".txt";
    file_put_contents($tmpfile, $foo);
    $size = filesize($tmpfile);
    unlink($tmpfile);
    return $size;
}

To rozwiązanie nie jest zbyt szybkie, ponieważ obejmuje operacje we / wy dysku, ale powinno dać ci coś znacznie dokładniejszego niż sztuczki memory_get_usage. Zależy to tylko od wymaganej precyzji.

Alan Bellows
źródło
Należy zauważyć, że to rozwiązanie działa tylko dla ciągów i jednowymiarowych tablic ciągów i że użycie strlenbyłoby łatwiejsze.
Adam
1
function mesure($var){
    $start = memory_get_usage();
    if(is_string($var)){
        $newValue = $var . '';
    }elseif(is_numeric($var)){
        $newValue = $var + 0;
    }elseif(is_object($var)){
        $newValue = clone $var;
    }elseif(is_array($var)){
        $newValue = array_flip($var, []);
    }
    return memory_get_usage() - $start;
}
Abdelilah
źródło
1

Poniższy skrypt przedstawia całkowite użycie pamięci przez jedną zmienną.

function getVariableUsage($var) {
  $total_memory = memory_get_usage();
  $tmp = unserialize(serialize($var));
  return memory_get_usage() - $total_memory; 
}

$var = "Hey, what's you doing?";
echo getVariableUsage($var);

Sprawdź to

http://www.phpzag.com/how-much-memory-do-php-variables-use/

vongani masanganye
źródło