20+ Consejos para optimizar tus programas PHP

Publicado por Alejandro Escario en

phpLa optimización de código es algo que ha preocupado a los programadores desde hace mucho tiempo, aunque por suerte o por desgracia, esta preocupación está decrementando día a día dado que los ordenadores con cada vez más potentes y por lo tanto importa menos el malgaste de ciclos de CPU.

Esta preocupación por el tiempo de ejecución de un programa viene dado sobre todo por el desarrollo de proyectos grandes (muchas visitas simultáneas) en los que sea crucial la velocidad de ejecución del algoritmo, por otro lado para aquellos proyectos que dada su escalabilidad pudiesen aumentar su complejidad de una forma un tanto brusca, es importante que el núcleo sea tan eficiente como sea posible además de, obviamente, tener el mínimo número de errores como sea posible, y por último puede que se dé el caso que en el caso de que tengas un servidor compartido, tu hosting te haya dado un toque porque haces uso de demasiados ciclos de CPU a la vez que te instan a migrarte a un plan de hosting más avanzado y que por consiguiente posea más recursos para tu script.

En este artículo vamos a intentar dar algunos consejos que permitan mejorar el rendimiento de nuestras aplicaciones web escritas en PHP con relativamente poco esfuerzo.

1.- Versión de PHP

Si estás utilizando una versión antigua de PHP será conveniente que hicieses una actualización de la misma en tu servidor, esto es así porque con cada versión, el equipo desarrollador de PHP ha ido implementando nuevas funciones así como optimizando partes de código, llegando a, reducir notablemente el tiempo de ejecución y la memoria consumida por determinados scrips y funciones de las que vienen por defecto.

A día de hoy, las últimas versiones estables de PHP son la 5.3.0 y la 5.2.11, eso sí, en ningún caso recomiendo instalar en un servidor en producción una versión que no sea estable.

2.- Utilizar algún sistema de Cacheado

En lenguajes interpretados, como es PHP, cada vez que se desea ejecutar un script, éste ha de ser interpretado, lo que hace que el consumo de CPU se incremente más que si se tratase de un lenguaje compilado, además de que ha de ejecutar todas las operaciones y funciones cada vez que se ejecute, lo que hace que si éstas son complejas, el script se ralentice considerablemente; un ejemplo de estas funciones son las llamadas a la base de datos.

A pesar de que no es recomendable cachear todas las páginas en algunos proyectos, en ocasiones es bastante recomendable utilizar aplicaciones como Memcache para así evitar que se ejecute el script siempre que se hace una petición de una página.

Otra opción es hacer uso de sistemas de templates, como es Smarty, que hace un cacheado de la página pero ejecutando siempre las consultas a la base de datos.

3.- Optimizar el código

3.1.- Strings

En PHP las cadenas de caracteres (Strings) pueden, al contrario que en lenguajes como C o Java, ir rodeados, tanto de comillas dobles («) como de comillas simples (), pero a pesar de que el resultado pueda llegar a ser el mismo, el comportamiento de una cadena delimitada por uno u otro de estos caracteres se comporta de manera diferente.

En PHP las cadenas de caracteres delimitadas por comillas simples requieren un consumo inferior de ciclos de CPU, esto es así, porque las cadenas encerradas por comillas dobles, analizan el string carácter a carácter en búsqueda de variables en PHP para sustituirlas por su valor, gracias a lo cual se evita tener que estar concatenando cadenas con variables, además si quieres imprimir un símbolo del dólar, tendrás que escaparlo($).

En conclusión, aunque nos suponga un poco más de trabajo el hecho de andar concatenando cadenas de caracteres, merece la pena en cuanto a rendimiento.

Ejemplo:

<?php

$m = 'mundo';

echo "hola $m !";

?>
<?php

$m = 'mundo';

echo hola ' . $m . ' !'; // hola mundo!

?>

Como podrás comprobar la salida de ambos código es hola mundo! pero la segunda forma de mostrar el mensaje es más eficiente que la primera, como hemos comentado anteriormente.

3.2.- Impresión de caracteres

Siempre que sea posible intenta imprimir todo aquello que no sea una variable y pueda imprimirse «tal cual» como código HTML, de manera que así evitamos que PHP analice ese área de texto y por lo tanto simplemente lo coloque en el buffer de salida, a su vez si hemos de imprimir algo que no es posible hacerlo con código HTML, entonces hemos de elegir cuidadosamente como hacer esto.

Hay dos funciones muy utilizadas para imprimir textos por pantalla, las cuales son echo y print, siendo la pirmera de estas la más rápida en realizar la misma tarea, además si deseamos imprimir varios textos, uno detrás de otro, si hacemos uso echo, podemos evitar la operación de concatenación (cancatenar es una función muy costosa) ya que echo admite varios parámetros.

Ejemplos:

<?php

$m = 'mundo';

$ex = '!';

?>

Hola <?php echo $m . $ex;?>

Hola <?php print($m . $ex);?>

Hola <?php echo $m, $ex;?>

Por otro lado, si deseamos evaluar lo sucedido dentro de la función es conveniente hacer uso de print(), ya que esta última devuelve un valor indicando cómo ha ido la operación.

3.3.- Reducir las peticiones SQL

Trabajar con una base de datos como por ejemplo MySQL en nuestras aplicaciones web programadas en PHP es bastante común, ya que esto nos permite interactuar con los usuarios de nuestra página así como llevar estadísticas actualizadas al momento entre otros.

Pero no todo lo que reluce es oro, a cambio de esta gran versatilidad que nos da combinar PHP con un SGBD (sistema de gestión de bases de datos) castiga fuertemente la rapidez de ejecución del script, por lo que minimizar lo máximo posible el número de consultas a la base de datos puede ser un hecho crucial, para hacer eso, podemos:

  • Cruzar tablas en la propia sentencia SQL.
  • Hacer JOINS entre distintas tablas antes que ejecutar más de una query para obtener todos los datos necesarios.
  • A la hora de insertar más de un elemento en la base de datos, evitar hacerlo dentro de un bucle o ejecutando secuencialmente varias.
<?php

// opción menos óptima

foreach ($table as $item) {
mysql_query('INSERT INTO A (aa,ab) VALUES("' . $item['aa'] . '", "' . $item['ab'] . '")');
 }

// opción más óptima

$data = array();
 foreach ($table as $item) {
$ata[] = '("' . $item['aa'] . '", "' . $item['ab'] . '")';
 }
 $query = 'INSERT INTO A (aa,ab) VALUES' . implode(',', $data);
 mysql_query($query);

?>

El contenido de la variable $query de la opción óptima será:


INSERT INTO A (aa, ab) VALUES (aa1, ab1), (aa2, ab2), ... , (aan, abn)

3.4.- Usar las funciones de PHP

Siempre que sea posible haz uso de las funciones que ya están implementadas en PHP en lugar de implementarlas tu mismo, ya que las implementadas en PHP son funciones muy testeadas y además muy optimizadas, además, ¿para qué calentarnos la cabeza en hacer algo que ya está hecho y además de uso gratuito?

Eso sí, aun así sigue siendo más eficaz hacer uso de constantes predefinidas que llamar a funciones que calculen su valor aunque estas últimas estén implementadas en el propio motor de PHP, como por ejemplo sería el caso de la función pi().

3.5.- Elimina los dato no necesarios

PHP tiene un recolector de basura (garbage collector) propio, el cual va liberando automáticamente la memoria inutilizada para evitar hacer un gasto innecesario de memoria, pero los algoritmos de estos «recogedores de basura» son complicados y no son ni mucho menos perfectos, así que si le ayudamos un poco y además así maximizaremos la memoria disponible en el momento deseado.

Para liberar estos recursos podemos hacer uso de la variable unset($variable) para así liberar la memoria.

3.6.- split() vs. explode()

A pesar de que split() es capaz de reconocer expresiones regulares y partir una cadena de caracteres utilizando uno de estos patrones, es más lento que explode(), por lo que siempre que sea posible ha de usarse esta última. Además de esto, la función split(), en la versión 5.3 de PHP está en desaprobación (deprecated).

3.7.- Inclusión de archivos

Las funciones include(), require(), include_once() y require_once() son funciones muy utilizadas en los proyectos PHP ya que estas nos permiten explotar la modularidad a la hora de programar, pero no todas ellas se comportan de la misma manera (require() devuelve un fatal error mientras que include() devuelve un warning), y es que include_once() y require_once(), antes de hacer la inclusión del código del nuevo fichero comprueba si éste ya ha sido añadido con anterioridad dentro del mismo script, lo que lo hace más lento que include() y require().

Por último en el caso de que el archivo a incluir no tenga porqué ser analizado, podemos hacer uso de la función readfile(), esta función coloca el archivo indicado en el buffer de salida directamente. Ésto es bastante útil si queremos permitir la descarga de un archivo sin que se conozca la URL real del archivo que estamos descargando.

Ejemplo:

obtenido de php.net

<?php
$file = 'monkey.gif';

if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename='.basename($file));
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file));
    ob_clean();
    flush();
    readfile($file);
    exit;
}
?>

3.8.- Incrementos

Una de las operaciones matemáticas más utilizadas en cualquier lenguaje de programación es la de incrementar, ya que ésta es utilizada en muchos de los bucles que se ejecutan en el programa.

Hay dos tipos de incrementos:

  • $i++
  • ++$i

Ambas operaciones incrementan en una unidad el valor de la variable ($i en nuestro caso), la única diferencia es que $i++ incrementa después de devolver el valor de $i, mientras que ++$i incrementa antes de devolver el valor de $i, pues bien, la operación de pre-incremento (++$i) consume un opcode menos que la de post-incremento ($i++);

Ejemplo:

<?php

$i = 0;

echo $i;  // 0

echo ++$i; // 1

echo $i++; // 1

echo ++$i; // 3

?>

Como último consejo para este apartado, también es más aconsejable hacer uso de ++$i que $i += 1;

Para una explicación más detallada, puedes ver un análisis más detallado de esta optimización en el artículo ¡Deja de usar i++ en tus bucles de PHP!

3.9.- Bucles

Los bucles son un arma de doble filo, por un lado nos facilitan mucho la vida permitiéndonos ejecutar una o varias acciones de forma repetida, pero por otro lado, los bucles pueden medrar en la optimicidad del código, por ejemplo el hecho anidar bucles, hace que el número de veces que se ejecutará el código del interno venga determinado por el número de vueltas que da el bucle externo por el número que da el interno, lo que de nuevo puede disparar el consumo de ciclos de CPU y de memoria, ya analizaremos en otro post como medir la complejidad de un algoritmo.

A pesar de que al usar un bucle puede convertirse en la peor de nuestras pesadillas (no suele ser así) podemos mejorar el rendimiento de los mismos levemente eligiendo correctamente qué tipo de bucle utilizar, a continuación listaré los tipos de bucles ordenados de menor a mayor coste:

  • do{ … }while( … )
  • while( … ) { … }
  • for( … ; … ; … ) { … }

3.10.- Variables estáticas

Hacer uso de variables estáticas hace que al asignarle el valor de una a otra en lugar de copiarse el contenido de la misma, lo que se copia es una referencia al contenido, es decir que el contenido de la variable sólo está almacenado una vez en memoria, lo que puede llegar a ahorrar una cantidad interesante de memoria.

3.11.- Constantes

Las constantes son utilizadas para valores no variables, ésto hace que el comportamiento de los compiladores (intérprete en este caso), en lugar de meter en memoria el valor como una variable, lo que se hace es sustituir directamente las cadenas que identifican la constante por su valor.

3.12.- Evita la copia de variables

La copia o asignación de variables no estáticas producen una copia del contenido de la una en la otra, lo que hace que se multiplique por n el espacio en memoria utilizado para almacenar en muchas ocasiones la misma información, ya que si bien muchas veces la copia de una variable se realiza con el fin de tener una copia de seguridad, otras muchas se hace simplemente para mejorar la legibilidad del código, un ejemplo muy claro de este último hecho es almacenar el contenido de las variables pasadas de una página a otra mediante el uso de los métodos GET o POST para así tener que escribir menos para utilizarla y a su vez para que la comprehensión del código mejore en la medida de los posible.

3.13.- Comprobación de variables

Comprobar la existencia de una variable es muy útil, por ejemplo se hace uso de esta estrategia con el fin de evitar la impresión de mensajes de Warnings de PHP.

Siempre que queramos comprobar la existencia de una variable, es preferible hacer uso de las funciones en el orden en el que voy a escribirlas a continuación:

Por último decir que es más rápido acceder a una variable es más rápido que comprobar si un elemento de un array existe, por lo que para recorrer un array con un bucle es aconsejable guardar la longitud del mismo en una variable para hacer las comparaciones una vez lleguemos al bucle.

Ejemplo:

<?php

$i = 0;
 $length = count($array);
 while($i < $lenght){
++$i;
 }

?>

3.14.- Supresión de errores

El uso de la «@» antes de una linea que suele lanzar un Error o Warning es una técnica de uso muy habitual, pero ésta es a su vez una medida muy costosa, lo que hace que aunque lleve más trabajo sea más aconsejable hacer uso de la siguiente táctica:

<?php

/*function() es la función a ejecutar mientras que siDaError() representa al conjunto de acciones que se han de han de ejecutar en caso de error*/

function() or siDaError();

?>

3.15.- Operadores

Si ahora nos centramos en los operadores de comparación, podemos distinguir dos grandes grupos:

  • ==, !=, <=, =>
  • ===, !==, <==, ==>

El primer grupo para ver si dos elementos son iguales, simplemente comprueba su valor, razón por la cual si se evalua una condición del tipo:

&amp;amp;lt;?php

if(true == 1){ ... }

?&amp;amp;gt;

El resultado de la comparación sera true, por l que entrará en la sección entre corchetes en lugar de saltarsela y/o irse al else.

El segundo grupo además de comparar los valores también compara los tipos de datos, lo que hace que sea un poco más lento que la primera opción.

3.16.- Alias

Un alias no es más que un segundo nombre que se le da a una función, el uso de éstos es considerablemente más lento, ya que tiene que llamar a la función principal.

En la siguiente lista os mustro algunos de los alias de algunas funciones (en negrita):

  • chop -> rtrim
  • close -> closedir
  • die -> exit
  • dir -> getdir
  • diskfreespace -> disk_free_space
  • fputs -> fwrite
  • ini_alter -> ini_set
  • is_writeable -> is_writable
  • join -> implode
  • pos -> current
  • rewind -> rewinddir
  • strchr -> strstr
  • sizeof -> count

3.17.- If/Else vs. Switch

Es preferible hacer uso de If/Else a utilizar Switch, ya que eon más rápidos.

3.18.- Bufferes

Esto realmente no es un hecho de ahorrar ciclos de CPU, pero un uso correcto de los mismos nos permitirá crear una sensación de velocidad de carga en el usuario final de la página web, ya que podemos forzar la impresión de todo lo que se halle en el buffer hasta el momento, permitiendo así que el usuario sea capaz de visualizar su contenido.

3.19.- Valor y Referencia

Cierto es que ésta no se trata de una característica de PHP, ya que C y otros lenguajes también permiten pasar variables a funciones por valor o por referencia, pero ¿cuál es la diferencia?

Pasar una variable por valor fuerza la creación de una nueva variable con el mismo valor que la del parámetro correspondiente, lo que a su vez es bastante interesante porque nos permite modificar el valor dentro de la función sin que este cambio se vea reflejad en la variable del hilo de ejecución principal.

Por otro lado pasar una variable por referencia consiste en pasarle a la función un puntero a la variable anterior, por así decirlo, no se produce una copia del valor, sino que las dos variables, la de dentro de la función y la de fuera apuntan al mismo area de memoria, lo que nos evita una horro en memoria y en tiempo, el único inconveniente de ésto es que si el valor de la variable se modifica dentro de la función, éste también quedará modificado fuera de la misma.

Ejemplo:

&amp;amp;lt;?php

/*paso por valor*/

function valor($a){

$a++;

}

/* paso por referencia */

function referencia(&amp;amp;amp;$a){

$a++;

}

/* programa */

$a=0; // $a = 0

valor($a); // $a = 0;

referencia($a); // $a = 1;

?&amp;amp;gt;

3.20.- Classes

El hecho de utilizar, acceder o modificar un atributo o un método de una clase es mucho más lento que hacer lo mismo con una función o variable local, por lo que hay que evaluar si es conveniente hacer uso de una clase o por otro lado no es necesario.

4.- Cuellos de Botella

Los cuellos de botella son los puntos más críticos de un script, aquellos puntos en los que se puede generar una cantidad ingente de actividad, lo que además de ralentizarlo cabría la posibiladad de que el consumo de memoria tabién se dispare en mayor o menor medida.

Identificar los cuellos de botella y solventarlos pueden aumentar la eficiencia del código enormemente.

Fuentes: Emezeta, tufuncion, Google

5.- Actualizaciones

  • 23/07/2021: se añaden enlaces demostrativos para las optimizaciones de incrementos

11 comentarios

arielmacintosh · noviembre 18, 2011 a las 11:20 pm

Gracias 🙂

Jonathan · octubre 19, 2012 a las 8:44 pm

muy buen aporte. pero la solución que das para «Reducir las peticiones SQL» solo acepta como máximo 1000 filas. osea que si tienes 1001 filas ya no aceptara la ultima y te mostrara un mensaje de error como el siguiente:

The number of row value expressions in the INSERT statement exceeds the maximum allowed number of 1000 row values

¿hay alguna otra alternativa a ese código que pones como ejemplo?

    esc_89 · diciembre 5, 2012 a las 11:57 am

    Realmente, no, que yo sepa, pero es muy fácil hacer un algoritmo que genere un insert en el que sólo haya 1000 filas,…

    si quieres un código de ejemplo, te lo puedo pasar

Iván · noviembre 12, 2013 a las 7:25 pm

Hola:
en el punto: 3.3.- Reducir las peticiones SQL, dices que es mejor hacer las peticiones en 1 única consulta que en varias. Bien, mi duda es (se que se me escapa algo pero no lo tengo claro)
He probado varios casos, uno en el que hacia una consulta con varios JOINS (unos 15) y otro donde hacia dos consultas (retire 10-12 JOINS y lo hice en una consulta nueva). Viendo el tiempo que tardaba antes y después de los cambios era, que en este caso es mejor hacer las dos consultas en lugar de una única consulta. (el tiempo de consulta era menor, teniendo en cuenta la suma de tiempo de las dos consulta).
El siguiente caso es: Necesitaba hacer unos COUNT, para lo cual, hacia 4 consultas (principalmente porque no sabía como hacerlo en una sola) luego buscando di con la solución, pero veo que la consulta tarda bastante.

Esto de hacer las peticiones en una única consulta, tiene sus limitaciones ¿no? en caso afirmativo, podrías decirme casos o ¿un sitio donde lo expliquen?

Un saludo

    Alejandro Escario · noviembre 21, 2013 a las 4:51 pm

    Buenas tardes:

    En cuanto a lo de agrupar o no consultas SQL para mejorar el rendimiento depende principalmente de dos aspectos:
    – Que esa única consulta esté bien hecha y no haga cruces innecesarios (es algo más común de lo que pueda parecer): no digo que sea tu caso, pero a mí sí que me ha pasado, y es que en ocasiones para ir de A a C pasamos por C y por D, cuando realmente A y B se pueden cruzar directamente o pasando sólo por C, lo que hace que la ejecución de la consulta se eternice. No sé si me explico.
    – Que los datos obtenidos por dichas consultas deban de ser cruzados: El tiempo de ejecución de una consulta puede ser mayor que el de hacer dos que obtengan los mismos datos, el quid de la cuestión es que si luego hay que unificar los dos dataset en uno sólo o «rellenar con los dos una tabla» haciendo uso de bucles, el tiempo suele ser mayor que el de una única consulta.

    En cualquier caso, en este mundo no existe respuesta absoluta y, si bien en este momento no me viene a la cabeza ninguna consulta que sea más eficiente hacerla en varias que en una sola, es muy probable que existan varias.

    Un saludo y perdón por tardar en responder

    Alejandro Escario · noviembre 21, 2013 a las 4:53 pm

    De todos modos, si quieres que le echemos un vistazo a tu caso concreto, no tienes más que comentármelo en más detalle 😉

      Iván · noviembre 21, 2013 a las 5:14 pm

      Hola:
      Una consulta que note que es indiferente hacerla en varias o en una es la siguiente

      SELECT
      COUNT(`accountID`) AS `accountsTotal`,
      (SELECT COUNT(`sessionID`) FROM `accounts` WHERE `sessionID` IS NOT NULL) AS `accountsOnline`,
      (SELECT COUNT(`characterID`) FROM `accounts` WHERE `characterID` IS NOT NULL) AS `accountsCharacters`,
      (SELECT COUNT(`state`) FROM `accounts` WHERE `state` = ‘0’) AS `accountsState`
      FROM
      `accounts`

      La única diferencia es que ahorro tiempo de proceso a PHP (porque ya no tengo que gestionar varias consultas) además que es más sencillo porque luego no tienes que juntar los resultados.
      No sé si la consulta está bien hecha o se podría perfeccionar he probado de varias formas y no he visto diferencia.

      La otra consulta, seria muy larga para ponerla, pero básicamente, es recuperar datos de una tabla y como la mayor parte de esos datos son IDs es hacer JOINS para cada uno y buscar su nombre, en total tenia unos 10 o 15, y luego me di cuenta de que todos esos JOINS iban siempre a la misma tabla y decidí hacer dos consultas, y note que el tiempo mejoraba, también me permitió trabajar con esos otros datos.

      P.D: no soy experto simplemente un aficionado que hace su web (puedes visitarla ^^) y dispone de un servidor bastante pequeño, y quiero que las páginas tarden lo menos posible en cargarse, al principio de todo (hace ya un par de años) hace una consulta para cada consulta a la tabla, porque no tenia ni idea de como funcionaban los JOINS, luego lo acabe entendiendo xD. Se que la respuesta también depende de como se hagan las consultas y también de como tengas hecha la base de datos, por eso siempre intento seguir la lógica humana, que es, si para un humano no es enrevesado para una maquina tampoco lo es.

      Un saludo y gracias por la respuesta.

        Alejandro Escario · noviembre 21, 2013 a las 5:26 pm

        Yo tampoco soy ningún experto en SQL, pero una de las cosas más ineficientes que hay son los llamados «select anidados», ya que la complejidad de esta operación, según tengo entendido, crece linealmente. En cualquier caso, si lo haces en una única consulta te ahorras los tiempos de solicitud y recepción de los datos desde la base de datos.

        En cuanto a la otra consulta, lo que dices tiene toda la lógica del mundo 🙂 si haces 15 joins y 10 de ellos son contra la misma tabla, estás haciendo que el motor de BD consulte esa tabla 10 veces con diferentes condiciones. ¡Me lo apunto como ejemplo!

        Luego le echo un vistazo a tu página 😉

JRD · julio 15, 2016 a las 7:18 pm

Solo quería dejar un simple pero agradable GRACIAS!. Muy útil su web.

Rafa Rodriguez · noviembre 16, 2016 a las 3:10 pm

Hola a todos:

Vean este test que hice para saber cuanto demora llamar a una funcion. Lo primero pregunta por TRUE, y lo segundo pregunta a una funcion por que devuelve TRUE.

$t = microtime(true);
for($i=0; $i<100000; $i++) if (true);
$t1 = microtime(true) – $t;
echo "\n";

function isn(){return true;}

$t = microtime(true);
for($i=0; $i<100000; $i++) if (isn());
$t2 = microtime(true) – $t;
echo "\n";

echo "$t1\n$t2\n";
echo ($t2 /($t1+$t2) * 100);
echo "\n";

El resuldato fue:

0.029117822647095
1.6365568637848
98.251890186945

Como que es mejor evitar siempre que se pueda hacer funciones (innecesarias) para todo.

Saludos,
rafa

    Alejandro Escario · noviembre 16, 2016 a las 5:46 pm

    Efectivamente Rafa, al final es como todo en Ingeniería, es un compromiso entre varias cosas y no siempre es fácil equilibrarlo. En cada ocasión hay que valorar qué compensa, si que el código sea más rápido o más legible.

Los comentarios están cerrados.