Come determinare l'impronta di memoria (dimensione) di una variabile?


102

Esiste una funzione in PHP (o un'estensione PHP) per scoprire quanta memoria utilizza una determinata variabile? sizeofmi dice solo il numero di elementi / proprietà.

memory_get_usageaiuta in quanto mi dà la dimensione della memoria utilizzata dall'intero script. C'è un modo per farlo per una singola variabile?

Nota che questo è su una macchina di sviluppo, quindi il caricamento di estensioni o strumenti di debug è fattibile.


Modificato: sono trascorsi 5 anni e alcuni problemi sono ancora in qualche modo irrisolti :(
Piskvor ha lasciato l'edificio il

Risposte:


46

Probabilmente hai bisogno di un Memory Profiler. Ho raccolto informazioni per SO, ma ho copiato alcune cose importanti che potrebbero aiutare anche te.

Come probabilmente saprai, Xdebug ha abbandonato il supporto per i profili di memoria dalla versione 2. *. Cerca la stringa "funzioni rimosse" qui: http://www.xdebug.org/updates.php

Funzioni rimosse

Rimosso il supporto per la profilazione della memoria in quanto non funzionava correttamente.

Altre opzioni di Profiler

php-memory-profilatore

https://github.com/arnaud-lb/php-memory-profiler . Questo è quello che ho fatto sul mio server Ubuntu per abilitarlo:

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

E poi nel mio codice:

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

Infine apri il callgrind.outfile con KCachegrind

Utilizzo di Google gperftools (consigliato!)

Prima di tutto installa Google gperftools scaricando l'ultimo pacchetto qui: https://code.google.com/p/gperftools/

Quindi come sempre:

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

Ora nel tuo codice:

memprof_enable();

// do your magic

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

Quindi apri il tuo terminale e avvia:

pprof --web /tmp/profile.heap

pprof creerà una nuova finestra nella sessione del browser esistente con qualcosa come mostrato di seguito:

Profilazione della memoria PHP con memprof e gperftools

Xhprof + Xhgui (il migliore secondo me per profilare sia cpu che memoria)

Con Xhprof e Xhgui puoi anche profilare l'utilizzo della CPU o solo l'utilizzo della memoria se questo è il tuo problema al momento. È una soluzione molto completa, ti dà il pieno controllo ei log possono essere scritti sia su mongo che nel filesystem.

Per maggiori dettagli vedere qui .

Fuoco nero

Blackfire è un profiler PHP di SensioLabs, i ragazzi di Symfony2 https://blackfire.io/

Se usi puphpet per configurare la tua macchina virtuale sarai felice di sapere che è supportato ;-)

Xdebug e traccia dell'utilizzo della memoria

XDEBUG2 è un'estensione per PHP. Xdebug ti consente di registrare tutte le chiamate di funzione, inclusi i parametri e restituire valori a un file in diversi formati. Ci sono tre formati di output. Uno è inteso come una traccia leggibile dall'uomo, un altro è più adatto per i programmi per computer poiché è più facile da analizzare e l'ultimo utilizza HTML per la formattazione della traccia. È possibile passare tra i due diversi formati con l'impostazione. Un esempio sarebbe disponibile qui

forp

forp profiler PHP semplice, non intrusivo, orientato alla produzione. Alcune delle caratteristiche sono:

  • misurazione del tempo e memoria allocata per ciascuna funzione

  • uso della CPU

  • file e numero di riga della chiamata di funzione

  • output come formato Trace Event di Google

  • didascalia delle funzioni

  • raggruppamento di funzioni

  • alias di funzioni (utili per funzioni anonime)

DBG

DBG è un debugger php completo, uno strumento interattivo che ti aiuta a eseguire il debug degli script php. Funziona su un server WEB di produzione e / o sviluppo e ti consente di eseguire il debug degli script in locale o in remoto, da un IDE o da una console e le sue caratteristiche sono:

  • Debug locale e remoto

  • Attivazione esplicita e implicita

  • Stack di chiamate, incluse chiamate di funzione, chiamate a metodi dinamici e statici, con i relativi parametri

  • Navigazione attraverso lo stack di chiamate con possibilità di valutare le variabili nei posti corrispondenti (annidati)

  • Funzionalità Step in / Step out / Step over / Run to cursor

  • Punti di interruzione condizionali

  • Punti di interruzione globali

  • Registrazione di errori e avvisi

  • Più sessioni simultanee per il debug parallelo

  • Supporto per GUI e front-end CLI

  • Reti IPv6 e IPv4 supportate

  • Tutti i dati trasferiti dal debugger possono essere opzionalmente protetti con SSL


2
Questa è esattamente l'informazione che stavo cercando, grazie.
Piskvor ha lasciato l'edificio il

93

Non esiste un modo diretto per ottenere l'utilizzo della memoria di una singola variabile, ma come suggerito da Gordon, è possibile utilizzarlo memory_get_usage. Ciò restituirà la quantità totale di memoria allocata, quindi è possibile utilizzare una soluzione alternativa e misurare l'utilizzo prima e dopo per ottenere l'utilizzo di una singola variabile. Questo è un po 'complicato, ma dovrebbe funzionare.

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

Nota che questo non è in alcun modo un metodo affidabile, non puoi essere sicuro che nient'altro tocchi la memoria durante l'assegnazione della variabile, quindi questo dovrebbe essere usato solo come approssimazione.

Puoi effettivamente trasformarlo in una funzione creando una copia della variabile all'interno della funzione e misurando la memoria utilizzata. Non l'ho testato, ma in linea di principio non ci vedo niente di sbagliato:

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

14
$tmp = $varcreerà una copia superficiale. Questo non allocherà più memoria finché $ tmp non viene modificato.
Gordon

@ Gordon, hai ragione, ho trascurato quel punto. Poiché non riesco a capire un modo corretto per modificare la variabile senza cambiarne il tipo o la dimensione, lo lascerò. Forse qualcuno può avere un'idea corretta :)
Tatu Ulmanen

7
che ne dici di $tmp = unserialize(serialize($var)); Questo combinerebbe l'approccio di Aistina sopra.
Gordon

3
inoltre, poiché $varè già una copia superficiale o un riferimento di ciò che è stato passato alla funzione, non è necessario $tmp, ma è possibile riassegnare a $var. Ciò salva il riferimento interno da $tmpa $var.
Gordon

Non c'è un modo più elegante per dereference $tmpda $var?
Tomáš Zato - Ripristina Monica il

24

No non c'è. Ma puoi serialize($var)controllare strlenil risultato per un'approssimazione.


Questo è un approccio molto migliore, poiché evita l'intera faccenda del GC.
Gleno

12
È una terribile approssimazione. Ogni elemento in un array in PHP è ~ 80 byte, ma strlen(serialize(array(1,2,3)))è 30.
gsnedders

2
@Aistina, -1. stai misurando la cosa sbagliata. La variabile e la variabile serializzata sono due cose completamente diverse e daranno risultati completamente diversi.
Pacerier

1
Non solo, ma fallirà completamente su alcune strutture dati non serializzabili, ad esempio riferimenti circolari.
duskwuff -inattivo-

20

In risposta alla risposta di Tatu Ulmanens:

Va notato che esso $start_memorystesso occuperà memoria ( PHP_INT_SIZE * 8).

Quindi l'intera funzione dovrebbe diventare:

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

Mi dispiace aggiungerla come risposta aggiuntiva, ma non posso ancora commentare una risposta.

Aggiornamento: * 8 non è definito. Apparentemente può dipendere dalla versione php ed eventualmente da 64/32 bit.


4
Puoi spiegare perché * 8? Grazie!
sierrasdetandil

@sierrasdetandil Sembra che $ start_memory non occupi solo PHP_INT_SIZEbyte, ma PHP_INT_SIZE*8. Puoi provarlo chiamando questa funzione, dovrebbe restituire 0:function sizeofvar() { $start_memory = memory_get_usage(); return memory_get_usage() - $start_memory - PHP_INT_SIZE*8; }
per

8non sembra costante. Seguendo la tua funzione di commento sul mio sistema di sviluppo (PHP 5.6.19), ritorna -16. Inoltre, è interessante notare che da php -a, la chiamata delle due righe della funzione fornisce vari valori diversi.
Paul DelRe

@PaulDelRe sì, probabilmente dipende dalla versione / 64bit di questo tipo di cose.
para

ora l'errore fatale si verifica alla chiamata unserialize (). Non è d'aiuto! Se una variabile è così grande da esaurire la memoria, chiamare una funzione su quella variabile utilizzerà ancora PIÙ memoria. :(
john ktejik

4

Vedere:

Nota che questo non ti darà l'utilizzo della memoria di una variabile specifica però. Ma puoi inserire chiamate a queste funzioni prima e dopo l'assegnazione della variabile e quindi confrontare i valori. Questo dovrebbe darti un'idea della memoria utilizzata.

Potresti anche dare un'occhiata all'estensione PECL Memtrack , anche se la documentazione è un po 'carente, se non per dire, praticamente inesistente.


Sì. Può essere utilizzato indirettamente per rispondere alla domanda.
elenco il

3

È possibile scegliere di calcolare la differenza di memoria su un valore di ritorno di callback. È una soluzione più elegante disponibile in 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;

3

Non è possibile calcolare retrospettivamente l'impronta esatta di una variabile poiché due variabili possono condividere lo stesso spazio allocato nella memoria

Proviamo a condividere la memoria tra due array, vediamo che allocare il secondo array costa metà della memoria del primo. Quando disinseriamo il primo, quasi tutta la memoria è ancora utilizzata dal secondo.

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)

Quindi non possiamo concludere che il secondo array utilizzi metà della memoria, poiché diventa falso quando disattiamo il primo.

Per una visione completa di come viene allocata la memoria in PHP e per quale utilizzo, ti consiglio di leggere il seguente articolo: Quanto sono veramente grandi gli array (e i valori) PHP? (Suggerimento: GRANDE!)

Le nozioni di base sul conteggio dei riferimenti nella documentazione PHP contengono anche molte informazioni sull'uso della memoria e i riferimenti contano per il segmento di dati condivisi.

Le diverse soluzioni qui esposte sono buone per le approssimazioni, ma nessuna può gestire la sottile gestione della memoria PHP.

  1. calcolo dello spazio appena assegnato

Se vuoi lo spazio appena assegnato dopo un compito, devi usarlo memory_get_usage()prima e dopo l'assegnazione, poiché usarlo con una copia ti dà una visione errata della realtà.

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

Ricorda che se vuoi memorizzare il risultato del primo memory_get_usage(), la variabile deve già esistere prima, ememory_get_usage() deve essere chiamata un'altra volta precedente, e anche ogni altra funzione.

Se si desidera eseguire l'eco come nell'esempio precedente, il buffer di output deve essere già aperto per evitare la memoria di contabilità necessaria per aprire il buffer di output.

  1. calcolo dello spazio richiesto

Se vuoi fare affidamento su una funzione per calcolare lo spazio richiesto per memorizzare una copia di una variabile, il seguente codice si occupa di diverse ottimizzazioni:

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

Notare che la dimensione del nome della variabile è importante nella memoria allocata.

  1. Controlla il tuo codice !!

Una variabile ha una dimensione di base definita dalla struttura C interna utilizzata nel codice sorgente PHP. Questa dimensione non fluttua nel caso dei numeri. Per le stringhe, aggiungerebbe la lunghezza della stringa.

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;

Se non teniamo conto dell'inizializzazione del nome della variabile, sappiamo già quanto usa una variabile (in caso di numeri e stringhe):

44 byte nel caso dei numeri

+ 24 byte nel caso di stringhe

+ la lunghezza della stringa (incluso il carattere finale NUL)

(questi numeri possono cambiare a seconda della versione PHP)

È necessario arrotondare per eccesso a un multiplo di 4 byte a causa dell'allineamento della memoria. Se la variabile si trova nello spazio globale (non all'interno di una funzione), allocherà anche 64 byte in più.

Quindi se vuoi usare uno dei codici all'interno di questa pagina, devi controllare che il risultato utilizzando alcuni semplici casi di test (stringhe o numeri) corrisponda a quei dati tenendo conto di ognuna delle indicazioni in questo post ($ _GLOBAL array, prima chiamata di funzione, buffer di output, ...)


1
... e questo è anche prima di entrare nei meccanismi interni di zvalue, is_refe copy-on-write. Grazie.
Piskvor ha lasciato l'edificio il

1
Grazie a te, ho perso quella pagina nel manuale PHP. Ho aggiunto il link per completare la mia risposta (ma immagino tu l'abbia già letto).
Adam

2

Ho avuto un problema simile e la soluzione che ho usato è stata scrivere la variabile su un file e quindi eseguire filesize () su di esso. Più o meno come questo (codice non testato):

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

Questa soluzione non è molto veloce perché coinvolge l'IO del disco, ma dovrebbe darti qualcosa di molto più preciso dei trucchi memory_get_usage. Dipende solo dalla precisione richiesta.


Va notato che questa soluzione funziona solo per stringhe e array di stringhe a dimensione singola e che l'utilizzo strlensarebbe più semplice.
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;
}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.