¿Cómo determinar la huella de memoria (tamaño) de una variable?


102

¿Existe una función en PHP (o una extensión de PHP) para averiguar cuánta memoria usa una variable determinada? sizeofsolo me dice el número de elementos / propiedades.

memory_get_usageayuda porque me da el tamaño de memoria utilizado por todo el script. ¿Hay alguna forma de hacer esto para una sola variable?

Tenga en cuenta que esto está en una máquina de desarrollo, por lo que es factible cargar extensiones o herramientas de depuración.


Editado: han pasado 5 años y algunos problemas aún están algo sin resolver :(
Piskvor dejó el edificio el

Respuestas:


46

Probablemente necesite un Memory Profiler. He recopilado información para SO, pero he copiado algo importante que también puede ayudarlo.

Como probablemente sepa, Xdebug eliminó el soporte de creación de perfiles de memoria desde la versión 2. *. Busque la cadena "funciones eliminadas" aquí: http://www.xdebug.org/updates.php

Funciones eliminadas

Se eliminó la compatibilidad con la creación de perfiles de memoria, ya que no funcionaba correctamente.

Otras opciones del generador de perfiles

php-memory-profiler

https://github.com/arnaud-lb/php-memory-profiler . Esto es lo que hice en mi servidor Ubuntu para habilitarlo:

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

Y luego en mi código:

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

Finalmente abra el callgrind.outarchivo con KCachegrind

Usando Google gperftools (¡recomendado!)

En primer lugar, instale Google gperftools descargando el último paquete aquí: https://code.google.com/p/gperftools/

Entonces, como siempre:

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

Ahora en tu código:

memprof_enable();

// do your magic

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

Luego abre tu terminal y lanza:

pprof --web /tmp/profile.heap

pprof creará una nueva ventana en su sesión de navegador existente con algo como se muestra a continuación:

Perfilado de memoria PHP con memprof y gperftools

Xhprof + Xhgui (el mejor en mi opinión para perfilar tanto la CPU como la memoria)

Con Xhprof y Xhgui también puede perfilar el uso de la CPU o solo el uso de la memoria si ese es su problema en este momento. Es una solución muy completa, le brinda control total y los registros se pueden escribir tanto en mongo como en el sistema de archivos.

Para obtener más detalles, consulte aquí .

Fuego negro

Blackfire es un generador de perfiles PHP de SensioLabs, los chicos de Symfony2 https://blackfire.io/

Si usa puphpet para configurar su máquina virtual, le alegrará saber que es compatible ;-)

Xdebug y seguimiento del uso de memoria

XDEBUG2 es una extensión para PHP. Xdebug le permite registrar todas las llamadas a funciones, incluidos los parámetros y los valores de retorno, a un archivo en diferentes formatos. Hay tres formatos de salida. Uno está destinado a ser un rastro legible por humanos, otro es más adecuado para programas de computadora ya que es más fácil de analizar, y el último utiliza HTML para formatear el rastro. Puede cambiar entre los dos formatos diferentes con la configuración. Un ejemplo estaría disponible aquí

para p

forp simple, no intrusivo, orientado a la producción, perfilador PHP. Algunas de las características son:

  • medición de tiempo y memoria asignada para cada función

  • uso de CPU

  • archivo y número de línea de la llamada a la función

  • salida como formato de evento de seguimiento de Google

  • título de funciones

  • agrupación de funciones

  • alias de funciones (útil para funciones anónimas)

DBG

DBG es un depurador php con todas las funciones, una herramienta interactiva que le ayuda a depurar scripts php. Funciona en un servidor WEB de producción y / o desarrollo y te permite depurar tus scripts de forma local o remota, desde un IDE o consola y sus características son:

  • Depuración local y remota

  • Activación explícita e implícita

  • Pila de llamadas, incluidas las llamadas a funciones, llamadas a métodos dinámicos y estáticos, con sus parámetros

  • Navegación a través de la pila de llamadas con capacidad para evaluar variables en los lugares correspondientes (anidados)

  • Entrar / Salir / Pasar por encima / Ejecutar hasta la funcionalidad del cursor

  • Puntos de interrupción condicionales

  • Puntos de interrupción globales

  • Registro de errores y advertencias

  • Varias sesiones simultáneas para depuración paralela

  • Soporte para interfaces GUI y CLI

  • Soporta redes IPv6 e IPv4

  • Todos los datos transferidos por el depurador pueden protegerse opcionalmente con SSL


2
Esa es exactamente la información que estaba buscando, gracias.
Piskvor salió del edificio el

93

No hay una forma directa de obtener el uso de memoria de una sola variable, pero como sugirió Gordon, puede usar memory_get_usage. Eso devolverá la cantidad total de memoria asignada, por lo que puede usar una solución alternativa y medir el uso antes y después para obtener el uso de una sola variable. Esto es un poco complicado, pero debería funcionar.

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

Tenga en cuenta que este no es un método confiable de ninguna manera, no puede estar seguro de que nada más toque la memoria al asignar la variable, por lo que esto solo debe usarse como una aproximación.

De hecho, puede convertir eso en una función creando una copia de la variable dentro de la función y midiendo la memoria utilizada. No he probado esto, pero en principio, no veo nada malo en ello:

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

14
$tmp = $varcreará una copia superficial. Esto no asignará más memoria hasta que se modifique $ tmp.
Gordon

@Gordon, tienes razón, pasé por alto ese punto. Como no puedo encontrar una forma adecuada de modificar la variable sin cambiar su tipo o tamaño, lo dejo así. Quizás a alguien se le
ocurra

7
¿qué tal $tmp = unserialize(serialize($var)); Esto combinaría el enfoque de Aistina anterior.
Gordon

3
Además, dado $varque ya es una copia superficial o una referencia de lo que se pasó a la función, no es necesario $tmp, pero puede reasignarlo $var. Esto guarda la referencia interna de $tmpa $var.
Gordon

¿No hay alguna forma más elegante de eliminar la referencia $tmpa partir $var?
Tomáš Zato - Reincorporación a Monica

24

No no hay. Pero puede serialize($var)y verifique strlenel resultado para una aproximación.


Este es un enfoque mucho mejor, ya que evita todo el asunto de GC.
Gleno

12
Es una aproximación terrible. Cada elemento en una matriz en PHP tiene ~ 80 bytes, pero strlen(serialize(array(1,2,3)))tiene 30.
gsnedders

2
@Aistina, -1. estás midiendo algo incorrecto. La variable y la variable serializada son dos cosas totalmente diferentes y darán resultados completamente diferentes.
Pacerier

1
No solo eso, sino que fallará completamente en ciertas estructuras de datos no serializables, por ejemplo, referencias circulares.
anochecer-inactivo-

20

En respuesta a la respuesta de Tatu Ulmanens:

Cabe señalar que en $start_memorysí mismo ocupará memoria ( PHP_INT_SIZE * 8).

Entonces toda la función debería convertirse en:

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

Lamento agregar esto como una respuesta adicional, pero aún no puedo comentar una respuesta.

Actualización: El * 8 no está definido. Aparentemente, puede depender de la versión de php y posiblemente de 64/32 bits.


4
¿Puedes explicar por qué * 8? ¡Gracias!
sierrasdetandil

@sierrasdetandil Parece que $ start_memory no ocupa solo PHP_INT_SIZEbytes, sino que PHP_INT_SIZE*8. Puede intentarlo llamando a esta función, debería devolver 0:function sizeofvar() { $start_memory = memory_get_usage(); return memory_get_usage() - $start_memory - PHP_INT_SIZE*8; }
para

8no parece constante. Siguiendo su función de comentario en mi sistema de desarrollo (PHP 5.6.19), regresa -16. Además, curiosamente, php -allamar a las dos líneas de la función da varios valores diferentes.
Paul DelRe

@PaulDelRe sí, probablemente depende de la versión / 64bit este tipo de cosas.
para el

ahora el error fatal ocurre en la llamada unserialize (). ¡Eso no ayuda! Si una variable es tan grande que se queda sin memoria, llamar a una función en esa variable consumirá MÁS memoria. :(
john ktejik

4

Ver:

Sin embargo, tenga en cuenta que esto no le dará el uso de memoria de una variable específica. Pero puede realizar llamadas a estas funciones antes y después de asignar la variable y luego comparar los valores. Eso debería darle una idea de la memoria utilizada.

También puede echar un vistazo a la extensión Memtrack de PECL , aunque la documentación es un poco escasa, por no decir prácticamente inexistente.


Si. Puede usarse indirectamente para responder a la pregunta.
Notinlist

3

Puede optar por calcular la diferencia de memoria en un valor de devolución de llamada. Es una solución más elegante disponible en 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

No se puede calcular retrospectivamente la huella exacta de una variable, ya que dos variables pueden compartir el mismo espacio asignado en la memoria.

Intentemos compartir memoria entre dos matrices, vemos que asignar la segunda matriz cuesta la mitad de la memoria de la primera. Cuando desarmamos el primero, el segundo todavía usa casi toda la memoria.

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)

Entonces, no podemos concluir que la segunda matriz usa la mitad de la memoria, ya que se vuelve falsa cuando desarmamos la primera.

Para obtener una vista completa de cómo se asigna la memoria en PHP y para qué uso, le sugiero que lea el siguiente artículo: ¿Qué tan grandes son realmente las matrices (y valores) de PHP? (Pista: ¡GRANDE!)

Los conceptos básicos del recuento de referencias en la documentación de PHP también tienen mucha información sobre el uso de la memoria y el recuento de referencias al segmento de datos compartidos.

Las diferentes soluciones expuestas aquí son buenas para aproximaciones, pero ninguna puede manejar la administración sutil de la memoria PHP.

  1. calcular el espacio recién asignado

Si desea el espacio recién asignado después de una asignación, entonces debe usarlo memory_get_usage()antes y después de la asignación, ya que usarlo con una copia le da una visión errónea de la realidad.

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

Recuerda que si quieres almacenar el resultado del primero memory_get_usage(), la variable ya tiene que existir antes, ymemory_get_usage() debe ser llamada otra vez antes, y todas las demás funciones también.

Si desea hacer eco como en el ejemplo anterior, su búfer de salida ya debe estar abierto para evitar la memoria de contabilidad necesaria para abrir el búfer de salida.

  1. calculando el espacio requerido

Si desea confiar en una función para calcular el espacio requerido para almacenar una copia de una variable, el siguiente código se encarga de diferentes optimizaciones:

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

Tenga en cuenta que el tamaño del nombre de la variable importa en la memoria asignada.

  1. ¡Revisa tu código!

Una variable tiene un tamaño básico definido por la estructura interna de C utilizada en el código fuente PHP. Este tamaño no fluctúa en el caso de los números. Para cadenas, agregaría la longitud de la cadena.

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;

Si no tenemos en cuenta la inicialización del nombre de la variable, ya sabemos cuánto utiliza una variable (en el caso de números y cadenas):

44 bytes en el caso de números

+ 24 bytes en el caso de cadenas

+ la longitud de la cadena (incluido el carácter NUL final)

(esos números pueden cambiar según la versión de PHP)

Tiene que redondear a un múltiplo de 4 bytes debido a la alineación de la memoria. Si la variable está en el espacio global (no dentro de una función), también asignará 64 bytes más.

Entonces, si desea usar uno de los códigos dentro de esta página, debe verificar que el resultado usando algunos casos de prueba simples (cadenas o números) coincida con esos datos teniendo en cuenta cada una de las indicaciones en esta publicación ($ _GLOBAL array, primera llamada de función, búfer de salida, ...)


1
... y eso es incluso antes de entrar en los detalles internos de zvalue, is_refy copia en escritura. Gracias.
Piskvor salió del edificio el

1
Gracias a ti, me perdí esa página del Manual de PHP. Agregué el enlace para completar mi respuesta (pero supongo que ya lo habías leído).
Adam

2

Tuve un problema similar, y la solución que usé fue escribir la variable en un archivo y luego ejecutar () en él. Más o menos así (código no probado):

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

Esta solución no es terriblemente rápida porque implica E / S de disco, pero debería darte algo mucho más exacto que los trucos memory_get_usage. Solo depende de cuánta precisión requiera.


Cabe señalar que esta solución solo funciona para cadenas y matrices de cadenas de una sola dimensión y que su uso strlensería más fácil.
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;
}

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.