Sunday 28 November 2010

Medir consumo de RAM y tiempo para evaluar el rendimiento de tu script PHP/MySQL

¿Andas con la duda de si tu script de PHP o tus consultas a la base de datos MySQL necesitan una optimización para ganar tiempo de respuesta y ahorrar consumo de memoria? ¿Has recibido ya los primeros errores del tipo "Fatal error: Out of memory (allocated 3145728) (tried to allocate 491520 bytes) in /home/..."? Eso es que necesitas mejorar o bien la construcción/sintaxis de tus consultas a la base de datos, y/o gestionar mejor los bucles, las variables, los arrays y en general el uso de la memoria.


Más adelante prometo escribir algún consejo al respecto de la optimización, pero ahora empezaré por centrarme en cómo podemos comparar el antes y el después ;) Es decir, convendría que probases diversos modos de hacer tus consultas y gestionar las mismas, usando mientras lo haces algún tipo de medición de tiempo y de consumo de RAM, para después comparar los datos y saber cuál de los caminos que has construido es el más eficaz!!

Medir el consumo de memoria RAM en PHP



Hay dos funciones, pero para la intención que tenemos de optimización nos viene mejor ésta: memory_get_peak_usage(), que mide el máximo de memoria RAM que hemos hecho "hasta el momento". la usaremos así de sencillamente:

1  <?php
2  
3  $memo_ini
=memory_get_peak_usage();
4  
mi_funcion(); // se supone que esto es lo que consume muchos recursos
5  
$memo_fin=memory_get_peak_usage();
6  
7  echo 
"Usados un máximo de: ".round(($memo_fin $memo_ini)/(1024*1024),2). "Mb";
8  
9  
?>


En la $memo_ini y en $memo_fin almacenamos el uso máximo de RAM por nuestro "thread" (hilo?) PHP, con lo cuál haciendo la resta de ambos valores obtendremos la cantidad de memoria máxima añadida por la función "mi_funcion". El resultado lo obtenemos en bytes, por lo que lo pasamos a Mb dividiendo por (1024*1024) y lo pasamos a dos decimales (más que suficiente, aunque puedes darle más si necesitas más precisión).

Ten en cuenta que memory_get_peak_usage() devuelve el MÁXIMO de RAM consumida hasta el momento. Así que puedes imaginarte que si intentas medir consumos pequeños de RAM puede que te salga cero!! ¿Porque?

Pongamos un ejemplo. Cuando calculas el $time_ini has tenido un consumo máximo de 4Mb, sin embargo, tal vez ya descargarte parte de ese espacio espacio que ocupaste, por ejemplo con la función nativa unset(), y por tanto ahora mismo en RAM ocupas solo 2Mb. Así pues, si cuando llamas a "mi_funcion()", ésta consume por ejemplo 1Mb de RAM, entonces tu consumo en ese momento será de 2Mb+1Mb=3Mb... que es inferior a 4Mb. Así pues, cuando llames por segunda vez a la función memory_get_peak_usage() para calcular $memo_fin, te seguirá devolviendo el valor 4Mb !!!

Tal vez entonces te preguntes qué utilidad tiene y si no hay otra alternativa. Bueno, en cuanto a lo primero, claro que nos es útil! pues loq ue intentamos medir son "puntas" de consumo de RAM excesivas. Y en cuanto a lo segundo, claro que hay otra alternativa: la función que cité pero no mencioné arriba al inicio del artículo: memory_get_usage().

Esta segunda función mide EXACTAMENTE la memoria RAM ocupada en el momento que la llamas. Pero según cómo es más incómoda de usar. Te pondré un simple ejemplo: imagina que quieres medir el consumo de RAM del ejemplo que he puesto arriba, pero supon que "mi_funcion()" en realidad es un script de terceros al que no tenemos mucho acceso o simplemente no queremos gastar nuestro tiempo en analizar por dentro. Y supongamos además, que el script está medianamente bien hecho y se encarga de eliminar de la RAM cada una de las variables que va ocupando en sus cálculos.... así pues no podríamos saber cuánta memoria como máximo llega a usar, porque esta segunda función (memory_get_usage) posiblemente nos devolvería prácticamente la misma cantidad de memoria antes y después de llamar a "mi_funcion()". ¿Si me entiendes?

Conclusión... cada una de las dos funciones pueden ser útiles en según qué contextos, pero en general, creo que la primera nos será más cómoda a la par que suficiente.

Medir el consumo de tiempo de procesamiento



Bueno, este aspecto es mucho más sencillo. Mejor poner un ejemplo:

1  <?php
2  
3  $time_ini
=_microtime();
4  
mi_funcion(); // se supone que esto es lo que consume muchos recursos
5  
$time_fin=_microtime();
6  
7  echo 
"Procesado en: ".round(($time_fin $time_ini),2). " segs.";
8  
9  function 
_microtime(){
10  
// 0.41494500 1291000531 -> 1291000531.41494500
11  
list($usec$sec) = explode(" "microtime());
12  return ((float)
$usec + (float)$sec);
13  }
14  
15  
?>


Es decir, en lugar de usar la función nativa de PHP microtime() construimos una llamada _microtime(), porque la salida de la primera es un tanto especial (véase la primera línea comentada de la función que definimos).