Publi

Creando webs dinámicas con PHP. Un recorrido de más de 10 años hasta hoy

phpVShhvm

PHP es uno de los lenguajes de programación en el lado del servidor más popular y uno de los pilares en los que se apoyan cientos de millones de sitios web actualmente (aunque su uso no es exclusivo para web y puede darnos mucho juego).

Ya que PHP debe ser versátil y fácil de utilizar, se trata de un lenguaje interpretado o de script. Es decir, el código que programamos se escribe en un archivo de texto plano en un formato inteligible para los desarrolladores (aunque lento para los ordenadores). (Lo contrario sería un lenguaje que tenemos que compilar, esto es traducir el código escrito por el programador a código máquina que será muy fácil de entender para un ordenador, pero muy difícil de entender y aún más de modificar para un programador).

Una historia de PHP

Yo empecé con PHP allá por 2001-2002 con su tercera versión (aunque PHP4 ya estaba en la calle). El primer paso era hacer que un sitio mantuviera su esqueleto intacto cuando navegábamos por sus páginas. Es decir, una cabecera, una barra lateral y un pie que fueran fijos mientras navegamos (sin tener que copiar y pegar esta estructura en todas las páginas). Aunque no estábamos limitados a ello, podíamos crear sistemas de usuarios, encuestas, foros, enviar e-mails e incluso preparar zonas de administración internas en las webs en las que los administradores pudieran modificar diferentes aspectos de las páginas sin necesidad de escribir HTML.

Se abría un mundo de posibilidades, el problema, ahora era la CPU, los servidores de la época no eran tan rápidos y cuando el script era un poco complicado bajaba el rendimiento drásticamente, lo que significaba que podíamos atender a menos usuarios y les iría más lento a todos. Además, como hemos contado, PHP es un lenguaje interpretado, lo que a groso modo significa que un sistema informático, para procesar este código:

1
2
3
<?php
$name = $_GET['nombre'];
echo "Hola ".$nombre;

que parece aparentemente simple, nos obliga (internamente) a leer carácter a carácter “$nombre =” (¡ahh! Es una igualdad). “$_GET [” (es un array, ¿qué clave? “‘nombre’];” (ok, ahora busco una estructura de datos llamada $_GET, eso implica una búsqueda que pueden ser varios cientos de operaciones, luego cuando encuentre la estructura, hago una búsqueda de la clave nombre, lo que implica otros cientos o miles de operaciones, y todo eso lo guardo en una nueva estructura que llamo name. Tras ello, leo “echo” y busco dentro de las operaciones internas qué significa (otros cientos de operaciones). Luego leo las comillas, y veo que es una cadena de caracteres, tras ello, leo $nombre y tengo que buscar dentro de las estructuras de datos almacenadas qué es. Tras ello, hago una llamada interna a mi función “echo” que termine de realizar la tarea. Debemos notar que esto está bastante optimizado, ya que PHP está programado en C y éste último es muy rápido (si estuviera hecho en C++, por ejemplo, podría llegar a ser el doble de lento).

Al final, para ejecutar esas dos líneas de código podemos tener unas 10000 operaciones de CPU, lo que no es tanto si pensamos que un procesador de 1GHz es capaz de ejecutar 1000000000 (9 ceros), es decir 100000 veces esto en un segundo. Esto no sería malo, pero normalmente un script en PHP es mucho más grande y requiere operaciones más complejas que pueden ser miles de veces más grandes, y además, seguramente tengamos cientos de visitas simultáneas que estén pidiendo lo mismo (Imaginad, WordPress tiene cerca de 300.000 líneas de código, aunque no todas se ejecutan en cada petición)

Si el mismo código fuera compilado no tendríamos que realizar tantísimas operaciones cada vez que ejecutáramos el código, es decir, la primera vez, tendremos que leer carácter a carácter y determinar qué es cada cosa, pero las estructuras de datos se convertirían en direcciones de memoria (una sola operación para acceder a ellas, aunque los arrays sean más complicados, no sería para tanto), y cada operación tendría un código (y los ordenadores son muy rápidos con los números, no tanto con los textos), por lo que reduciríamos ese número unas 100 veces (comparándolo por ejemplo con un lenguaje como C).

Lo malo es que compilar ese código nos complicaría la vida a los programadores (los programadores antes se complicaban la vida), tenemos que tener en cuenta la arquitectura sobre la que trabaja el servidor, la versión de las bibliotecas de sistema e incluso dependencias con otras bibliotecas; con lo fácil que es subir una nueva versión del archivo y probarla rápidamente y además, podría ser inseguro para los administradores de servidores (no todo el mundo tiene buenas intenciones, y si dejamos que todos los programadores ejecuten lo que quieran en un servidor pueden ocurrir desastres muy gordos). Al final, tenemos que considerar un equilibrio: que la pérdida de rendimiento no sea espantosa, y que el desarrollo y mantenimiento sea sencillo; en este caso PHP tiene muchas herramientas de forma nativa para desarrollar casi cualquier cosa que por ejemplo en C sería muy costoso desarrollar, puede que en el desarrollo de una aplicación Web debamos invertir 100000€ más y el ahorro en materia de servidores sea de 1000€/año.

Los cuellos de botella

Hace unos años, uno de los mayores cuellos de botella en las aplicaciones web era el almacenamiento. Lo podíamos observar desde el punto de vista doméstico, en 2006 un disco duro de unos 100Gb costaba 80-100€ por lo que si necesitábamos almacenamiento a lo grande, debíamos crear un RAID y tal vez un cluster de servidores de almacenamiento. Además, tanto escrituras como lecturas no eran tan rápidas como ahora por lo que mucho tiempo se iba en entrada/salida (mucha información se cacheaba en RAM, pero tampoco había tanta memoria). Afortunadamente ahora encontramos discos SSD de 960Gb con los que tenemos una entrada/salida mucho más rápida y por supuesto más almacenamiento. Aunque si tenemos que mover grandes cantidades de información, tenemos que hacer clusters igualmente.

En la actualidad las aplicaciones web están obligadas a realizar muchas operaciones. Aunque parezcan muy sencillas, hay cientos de tareas que deben realizar por seguridad: filtrar todos los datos de entrada, comprobar datos de tu conexión, comprobar que el fichero de plantilla que van a colocar es realmente una plantilla y no ha sido suplantada, verificar lo que ha hecho el usuario en la web desde que entró y ver que su navegador y su IP coinciden y son buenas, además, tiene permiso para entrar en la zona donde acaba de entrar; luego antes de responder al navegador, establecen ciertas cabeceras de seguridad, comprimen la información, la cifran si es necesario, etc; y cuando tienen que almacenar información, mucha se almacenará cifrada, comprimida, replicada, etc, son muchas pequeñas cosas que se van sumando y a la vez se multiplican por la cantidad de usuarios simultáneos que tiene un sitio. Y para solucionar este problema se están aplicando varias técnicas: por un lado el hardware ha evolucionado, encontramos fácilmente servidores con 24 cores a 2.60GHz con más de 200Gb de RAM, aunque aún así con el creciente número de dispositivos que se conectan y webs complejas no es suficiente.

Versiones e implementaciones de PHP

Para resolver ese segundo cuello de botella en nuestras aplicaciones ha habido diferentes enfoques. El primero es que un programador en PHP utilice caché hasta la saciedad. Es decir, hay muchas cosas que puede optimizar, como la generación de páginas estáticas o de gráficas, podemos generarlas una vez cada minuto. Por ejemplo, si tenemos 10 peticiones por segundo, generaremos la información en la primera petición y en las siguientes 599 simplemente serviremos el dato generado. Lo mismo para peticiones SQL complejas (podemos almacenar el código SQL a enviar a base de datos), generación de imágenes (si necesitamos miniaturas o marcas de agua, sólo las metemos una vez), etc.

Con los años PHP ha evolucionado y ha ido mejorando la forma en la que trabaja, siendo más seguro y más rápido cada vez. Si nos damos cuenta, el trabajo de computación es muy repetitivo para PHP (para muchas de las peticiones procesamos siempre lo mismo o de la misma forma), en esos puntos podemos reducir algo de tiempo. Aunque por otro lado otras empresas han tomado cartas en el asunto.

En el 2010, Facebook comenzó sus andadas para optimizar esto. Facebook está programado en su mayor parte en PHP, y estaba creciendo mucho (si recordáis por aquella época solía haber muchos fallos), tenían un creciente número de usuarios y los centros de datos se les quedaban pequeños pronto, no daban abasto. Descubrieron que si Facebook estuviera programado en C++ ahorrarían costes, de hecho se redujo a la mitad, por lo que por el mismo precio podían atender al doble de usuarios. Eso lo consiguieron con un proyecto llamado Hip Hop for PHP[1], básicamente traducía el código PHP a C++, lo compilaba y lo ejecutaba. ¿ Por qué no programar directamente en C++ ? Porque es más barato y rápido desarrollar en PHP. C++ suele utilizarse en servidores para tareas pesadas que no requieran mucho mantenimiento y así ganar rendimiento; o con tareas que deban mover gran cantidad de datos (a partir de cientos de Mb), pero para código que esté en constante cambio y evolución como es la web, es mejor utilizar PHP. Además, programar Facebook de nuevo en C++ sería trabajo de chinos.

Tras este experimento, en 2013 llegó HHVM[2] (Hip Hop Virtual Machine) que al contrario que su predecesor no generaba un código C++ intermedio. Utilizando una técnica llamada compilado JIT (Just In Time compile, algo así como compilación justo a tiempo, refiriéndose a que se realiza en tiempo de ejecución) se transforma el código PHP en un código intermedio fácil de leer e interpretar por un ordenador. Parecido al código compilado en Java, con la diferencia de que no hay que compilarlo, cuando llega el programa se auto-compila y se ejecuta sin necesidad de hacer nada.

Por otro lado, desde finales de 2015 tenemos una nueva versión de PHP, PHP7, después de 14 años (ha habido actualizaciones a PHP5 todo este tiempo y sí, se han saltado PHP6). PHP7 utiliza una técnica parecida a HHVM, compilación JIT, además de incluir muchas mejoras en el lenguaje, como por ejemplo, la reescritura de varias estructuras de datos (como los arrays). PHP7 es la versión oficial y la que establece las normas del juego, por tanto HHVM debe adaptarse.

Entonces, ¿ HHVM o PHP7 ?

Además de estas dos implementaciones existen más (como Parrot[3] o Phalanger[4]), pero estas dos son las más famosas y las que mejores resultados obtienen. Pero si nos tenemos que decidir por una, todo depende de las aplicaciones que vayamos a ejecutar. Hay que comprender que PHP es un sistema complejo y se ha desarrollado mucho con él, por lo que puede que algunas desempeñen mejor en HHVM y otras en PHP7. Por otro lado, las últimas novedades siempre las tendrá PHP7 por lo que tendremos mejor soporte y tendrá mejor soporte para extensiones (las extensiones de PHP deben ser portadas a HHVM).
Pero, aunque HHVM vaya un paso por detrás de PHP7, podemos encontrar gran parte de las novedades del lenguaje ya implementadas, así que sólo es cuestión de probarlo. Es más, el equipo de desarrollo de PHP promete mejoras sustanciales en el rendimiento para PHP 7.1 y 7.2.

Personalmente, en 2014 intenté ejecutar un gran proyecto hecho para PHP con HHVM y hubo que realizar varios cambios que, derivaron en varios bugs de HHVM, aunque ha llovido mucho desde aquello y justo ahora lo he podido ejecutar sin problemas. Aunque no me he parado a hacer comparativas tiene un rendimiento similar a PHP7, pero todo depende del proyecto, de las visitas y muchos factores más, así que es mejor probarlo. Lo que sí está claro es que tanto en desarrollo como en producción es conveniente utilizar el mismo sistema (PHP en los dos o HHVM en los dos, pero nada de mezclar).

Aunque todo no es un camino de rosas, puede que necesites trabajar muy duro si tu actual web o aplicación está programada para PHP 5.1 o 5.3 ya que hay cambios sustanciales en el código PHP, muchas funciones obsoletas y palabras reservadas (cuidado con tener una clase que se llame String) que hay que cambiar. Aunque si quieres darle un poco más de vida a tu web, podrías probar con algunas versiones de HHVM que seguro que ejecutarán todo mucho más rápido.

Una forma fácil de empezar a probarlo

SiteGround-Logo
Una forma muy sencilla es utilizando el hosting de SiteGround que nos permite utilizar versiones de PHP desde la versión 5.3 a la 7.0 actualizadas hasta la última revisión y HHVM en sus planes de alojamiento en la nube. Estas opciones nos permitirán por un lado cambiar fácilmente la versión de PHP a medida que vamos adaptando nuestro código para tener un tiempo en línea mayor y por otro lado, probar nuevas versiones de PHP o HHVM para ver cómo es nuestra compatibilidad y ver con qué sistema funcionamos mejor.
SiteGround ha trabajado duramente en proporcionar a sus usuarios tanto HHVM como PHP7. Incluso este último desde las versiones beta con el objetivo de promocionarlo entre sus usuarios. Yo he utilizado PHP7 desde sus versiones Beta y funcionaba muy bien, sobre todo con software muy utilizado como WordPress y Drupal.
Es más, desde SiteGround, puedes acceder por SSH a tu servidor y probar las diferentes versiones de PHP desde un terminal desde PHP 4.9 hasta PHP 7.0 además de tener instalado python, ruby, compass, git, ImageMagick[5] y más.

Una forma más difícil de probarlo

También podéis instalar PHP7 en vuestro ordenador local, incluso HHVM. Si utilizáis Ubuntu 16.04 y derivados, tendréis una de las últimas versiones en el repositorio oficial, por lo que un simple:

sudo apt-get install hhvm

Os puede solucionar la vida. Aunque para PHP es más complicado, si queremos que varias versiones convivan en nuestro ordenador sin causar desastres, podéis probar PHPFarm.

Una pequeña comparativa

¿Os acordáis del código del principio? Vamos a hacer un pequeño experimento con él. Tenemos esto:

1
2
3
4
5
<?php
$nombre = "Poesía binaria";

for ($i=0; $i<100000000; ++$i)
    echo "Hola ".$nombre;

Esto lo voy a ejecutar con PHP 5.5.35, PHP 7.0.6 y HHVM 3.11.1 y esto es lo que obtengo:
PHP 5.5.35:

time php5 test.php > /dev/null
real 0m35.927s
user 0m26.372s
sys 0m9.500s

PHP 7.0.6:

time php7 test.php > /dev/null
real 0m28.158s
user 0m19.476s
sys 0m8.632s

HHVM 3.11.1:

time php7 test.php > /dev/null
real 0m14.321s
user 0m5.332s
sys 0m8.960s

Por lo que ahora mismo gana HHVM. Aunque podríamos probar ahora código en C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <string>

using namespace std;

int main(int argc, char* argv[])
{
    string nombre = "Poesía Binaria";

    for (long i=0; i<100000000; ++i)
        cout << "Hola "+ nombre + "\n";

    return 0;
}

No utilizo mucho los operadores /dev/null

real 0m7.354s
user 0m7.312s
sys 0m0.036s
[/simterm]

Y por último, mudemos el código de C:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main()
{
        char nombre[50] = "Poesía Binaria";
        long int i;
        for (i=0; i<100000000; ++i)
                printf ("Hola %s\n", nombre);

        return 0;
}

Resultando lo siguiente:

time ./testt > /dev/null
real 0m7.132s
user 0m7.024s
sys 0m0.084s

Muy parecido a C++ (compilado utilizando optimizaciones de procesador). Como vemos, aunque crear un código nativo sigue siendo mucho más rápido y será más óptimo, está muy bien utilizar lenguajes interpretados para muchas tareas por facilidad de programación y mantenimiento.

Referencias

Al ser un post patrocinado, los enlaces los pongo al final para que la página del anunciante sea lo primero que vea Google.

También podría interesarte...

There are 5 comments left Ir a comentario

  1. Pingback: Creando webs dinámicas con PHP. Un recorrido de más de 10 años hasta hoy | PlanetaLibre /

  2. jordila /
    Usando Mozilla Firefox Mozilla Firefox 45.0 en Linux Linux

    Gracias por compartir

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 50.0 en Ubuntu Linux Ubuntu Linux

      ¡Muchas gracias por tu comentario!

  3. Ivan /
    Usando Google Chrome Google Chrome 55.0.2883.87 en Windows Windows 7

    Intenta probar exactamente el mismo código en Java… a mi me otorgó un resultado desastroso, que me indica que Java no sería ideal para correr en las mismas condiciones que un PHP con HHVM o PHP7

    (Podrías hacer un post relacionado en el lenguaje de programación de la máquina HHVM)

  4. Pingback: Iteradores y generadores en PHP o por qué deberíamos utilizar yield más a menudo [con ejemplos] – Poesía Binaria /

Leave a Reply