Optimización de funciones PHP

Publicado en Optimización web( WPO ) hace 1 año y 5 meses. Leído 878 veces.

imagen destacada

He escrito muchos posts sobre optimización en la parte de frontend y muy pocos( si es que los hay ) sobre optimización en el backend. Así que hoy toca hablar de este último, más concretamente sobre el optimizar funciones PHP. Mostaré dos métodos, uno muy viejo( y quizás de sobra conocido porque además es multilenguaje ) y otro más nuevo. Ambos complementarios entre sí.

Como siempre, me serviré de varios ejemplo que me facilite la explicación. El ejemplo partirá de una función que construye un menú supuestamente de manera eficiente pero que no es así y, por tanto, la iremos optimizando poco a poco. Vamos allá:

Podemos ver la ejecución del código en: https://3v4l.org/K9feQ

Ya podemos ver la función con la que vamos a trabajar: get_menu(). Como se aprecia, se está tratando de simular un MVC. Además, se han añadido dos comentarios( que aparecerá en los logs ) que se ejecutan en el inicio y al final de la creación del menú. La creación se hace de manera directa para que sea más clara pero bien podría haberse obtenido a través de la base de datos o un servicio externo.

Esta forma de trabajar, que es muy habitual desgraciadamente, hace dos llamadas a get_menu() desencadenando que se realice la creación del menú también dos veces. Podría pasar que se llamara otras veces más a la función: porque se utilice la información del menú para las migas de pan, porque se quiere adaptar para que aparezca en el pie( recordemos que hay muchas páginas que repiten el menú en la cabecera y en el pie ), para usarlo también en la creación de un menú hamburguesa para móviles, ... y la función seguiría creando el mismo menú en cada llamada. Sin embargo, si la optimizamos:

Ahora, si nos fijamos en los logs, sólo veremos que se crea el menú una vez. El resto de las veces devuelve el valor de la variable estática $cache. Las variables estáticas de función, que existen en la mayoría de los lenguajes famosos( C, C++, JAVA, PHP, ... ), mantienen el mismo valor de llamada a llamada. Esto es, una vez que las creas, en las siguientes llamadas a la función, en vez de inicializarse y tomar el valor null( línea 10 ), lo que hace es tomar el valor que tenía cuando terminó la función en la llamada anterior. Así que nos sirve de una mini caché temporal. Obviamente, la próxima vez que PHP se lance volverá a reinicializarse su valor.

Lo mejor es que lo veas en: https://3v4l.org/WgKka. Fíjate que he añadido una llamada adicional a get_menu() para obtener el menú inferior y, sin embargo, en los logs aparece que sólo se crea una vez.

Aunque hemos solucionado nuestro problema, los ejemplos anteriores no son fiel a la realidad. Al menos desde el punto de vista de una web que quiere visualizar un menú. Lo normal en estos casos, en un MVC, es que la información del menú se pase a la vista y sea esta la que decide cuándo, y cuánto, visualizarlo.

Este es un ejemplo más real y, como dijimos, ahora es la vista quién decide cómo visualizarlo. Sin embargo, ahora que teníamos nuestro menú optimizado surge otro problema. ¿ Qué pasa si la vista decide no visualizarlo ?. Por ejemplo, en el código anterior, si estamos en portada no se visualiza. ¡ Vaya gracia !. Hemos creado un menú que no se va a utilizar. Es decir, nuestra función se va a ejecutar al menos una vez aunque sea en valde. ¿ Se puede hacer algo ?. Claro, por eso estás leyendo este post. Veamos: No te asuste. Aquí lo único que se ha hecho es tranformar nuestra querida función get_menu en un generador PHP. Un generador es como una función sólo que va paso a paso, como cuando usamos los puntos de interrupción de un IDE o del firebug, o como cuando estamos pasando de un operario a otro en una cadena de producción. Cada yield sería uno de esos puntos de interrupción o uno de esos operarios. Pero no vayamos tan rápido...

Lo primero que podemos ver es que se sigue creando el menú suponiendo que no estamos en portada, pero si cambiamos la línea 8 a true(y aparentamos estar en portada ), entonces ya el menú no se creará. También te habrás dado cuenta que la generación se sigue haciendo una sóla vez, por más que creemos bucles. Además, nos saltamos la limitación que tienen los generators de un sólo uso( también hay otras maneras de hacerlo ). Todo ello gracias a la vaiable estática que sigue haciendo su función estupéndamente.

Una vez en el bucle, de manera transparente e interna, en cada loop se realiza una llamada a la función que avanza un yield hacia delante hasta que llega al final de la función o se realiza un return. Para volver a iterar tenemos que obtener el valor del return.

Resumiendo: Hemos creado una función que cada vez que se llamaba generaba un menú, pero como era poco optima, y haciendo uso de variables estáticas, la convertimos en una función que sólo generaba el menú a pesar de que se llamara varias veces. Sin embargo, en algunos casos, nos podíamos ver obligados a que se generara el menú sin hacer uso de él. Así que, convertimos nuestra función en un generador que sólo entrará en funcionamieno cuando se vaya a usar el menú, y sino no. Además, por cada vez que se use, el menú ya estará generado y no hará falta crearlo de nuevo.

Espero que te haya gustado el post y si tiene alguna duda o aportación, tan sólo tienes que escribir un comentario.

4 comentarios

Muy buen artículo Manuel

Relacionado con esto me gustaría hacerte una pregunta: ¿Qué herramientas o métodos utilizas para medir el tiempo de ejecución de por ejemplo una función? Es decir, lo que tarda desde que es llamada hasta que devuelve el resultado.

Hace unos meses tuvimos que optimizar una página que devolvía un listado. Este listado se obtenía a través de una función que hacía multitud de comprobaciones y consultas a BBDD y tardaba unos 6 segundos.

Utilizamos microtime() al inicio y final de cada query y de cada foreach para 'medir' el tiempo que transcurría en cada caso. Lo pongo entre comillas porque microtime() en cada ejecución devolvía un valor diferente (realmente hacíamos 5 ejecuciones y sacábamos la media para hacernos una idea)

A base de ir optimizando consultas y bucles logramos reducir considerablemente el tiempo de ejecución, pero me quedé con la duda de si existe una herramienta o método mejor o más fiable para estos casos

Saludos!

Gracias, Pablo,

Lo que uso es microtime. Ten en cuenta que los tiempos nunca son exactos en un script y depende del número de visitas, la carga que tenga la cpu en cada momento, la memoria disponible, ... por eso la velocidad siempre hay que tomarla como referencia ya sea del script o de una web.

Hay herramientas de profiling de PHP pero aún no me dio tiempo probarlas. Cuando tenga tiempo para probar alguna ya escribiré sobre ello mis experiencias por si te sirviera de algo.

Gracias por acercarte por aquí y nos vemos la semana que viene ;)

Saludos!

Deja un comentario

Puedes usar Markdown para formatear tu comentario.

Este sitio utiliza cookies propias y de terceros para mejorar tu experiencia con el sitio web. Al continuar con la navegación consideramos que acepta su uso.