PHP 5.3 – Comparativa rendimiento static vs objetos instanciados

Me he decidido a hacer este post un poco en plan experimental ya que donde trabajo tenemos muchísimos métodos static en los modelos y siempre hay la discusión de si es mejor, si es peor, si es más cómodo, etc…

Está claro que siendo puristas, static debe usarse puntualmente y no de forma masiva. Hay problemillas en herencia de clases (solventadas en PHP5.3 con los Late Static Bindings) y el uso masivo de static dificulta mucho los testeos unitarios ya que no podemos tener mock objects instanciados.

Si estuvisteis en la pasada Barcelona PHP Conference se insistió bastante en este punto y un poco el lema era “If you need something, inject it”. Vamos que patrón Strategy y la Dependency Injection “reinventada” por Symfony por un tubo.

La idea que se me ha ocurrido es comparar la velocidad de llamar métodos estáticos con llamar métodos de objetos instanciados. Es importante tener en cuenta que en el segundo caso, también tenemos que instanciar el objeto, así que también hay que mirar este tema. También comprobaremos si hay diferencias importantes en objetos con muchos métodos respecto a objetos con pocos métodos.

Las pruebas las hago en mi portátil Compaq, con Ubuntu Lucid Lynx x64 y PHP 5.3.2. Desconozco si en otras versiones los resultados pueden diferir de forma importante, aunque imagino que no.

Las pruebas serán:

Objeto con 3 métodos -> (Instanciarlo y llamar a los 3 métodos) * 1000000 veces
Objeto con 3 métodos static -> (Llamar a los 3 métodos) * 1000000 veces
Objeto con 50 métodos static
Objeto con 50 métodos “normales” -> Repetir las dos pruebas anteriores

Me temo que el testeo no será 100% fiable ya que imagino que el SO realiza un cierto nivel de opcode caching pero bueno si suponemos un servidor linux dedicado 100% a servir Apache+PHP es bastante probable que en un entorno real tengamos un cacheo similar.

A ver qué sale!!!!!

Función para recuperar microtime correctamente formateado

function getmicrotime()
{
    list($usec, $sec) = explode(" ",microtime());
    return ((float)$usec + (float)$sec);
}

Objeto con 3 métodos static

<?php
class testStatic1 {
   public static function func1() {}
   public static function func2() {}
   public static function func3() {}
}
$a = getmicrotime();
for($i=0;$i<=1000000;$i++) {
   testStatic1::func1();
   testStatic1::func2();
   testStatic1::func3();
}
$b = getmicrotime();
echo '3 static '.$b-$a;
?>

El resultado de esto nos da aproximadamente 8.6 segundos.

Vamos a hacer lo mismo definiendo 50 métodos static en la clase a ver si el rendimiento empeora. El código sería el mismo pero creando func4 hasta func50.

Y el resultado es un poco superior pero no supera los 8.7 segundos, vaya que por meter muchos métodos static, el rendimiento es exactamente el mismo que si tenemos pocos.

Y a ver qué pasa si no es static y es un objeto instanciado.

Ahora el código quedaría algo así como:

<?php
class testInstance1 {
    public function func1() {}
    public function func2() {}
    public function func3() {}
}
$a = getmicrotime();
for($i=0;$i<=1000000;$i++) {
   $o = new testInstance1();
   $o->func1();
   $o->func2();
   $o->func3();
}
$b = getmicrotime();
echo '3 instanced '.($b-$a);
?>

Aquí el resultado nos dará 9.28 segundos y si definimos los 50 métodos… 9.37 segundos.

Con esto podríamos determinar que el empeoramiento de rendimiento al tener muchos métodos es prácticamente insignificante y afecta por igual a static que a instanced.

Vamos a ver ahora cuánto tiempo consume la instanciación del objeto (es decir, hacer el new) y cuanto tardan los métodos. Para ello, vamos a dejar dentro del bucle solamente el new testInstance1.

El resultado para un objeto de 3 métodos es 1.24 segundos de instanciar y el resto, 8.04 segundos serían las llamadas.
Para el objeto de 50 métodos, en la línea de lo anterior, 1.28 segundos de instancias y 8.09 segundos de llamadas.

Por tanto, con estos números podemos concluir que si vamos a hacer pocas llamadas al objeto, el rendimiento de static es ligeramente superior.

Vamos a ver qué pasa si en cada iteración, llamamos a los 50 métodos. Rebajaremos a 100mil iteraciones para no tirar apache.

Con esto el resultado en static es 14.5 segundos y en objeto instanciado es 14.0 segundos. Por tanto si usamos muchos métodos del objeto es algo mejor el rendimiento al tener métodos no estáticos.

Resumiendo:

  • Static es más rápido si usamos pocos métodos del objeto en nuestra transacción.
  • Objetos instanciados es más rápido si usamos bastantes métodos del objeto.
  • El hecho de tener muchos métodos penaliza bastante poco
  • En cualquier caso, si en las funciones realizamos operaciones complejas como acceder a bases de datos, ahí está el cuello de botella y es donde debemos invertir en optimizar

¿Qué es lo correcto? Pues es difícil de decir.

En aplicaciones web pequeñas, probablemente lo ideal a nivel rendimiento sea usar todo static. Normalmente usaremos pocos métodos de los modelos en cada página y estaríamos en el primer caso del experimento.
Además, en este tipo de páginas con un testeo funcional basta y salvo en casos puntuales no es necesario hacer testeos unitarios.

Sin embargo, en aplicaciones grandes puede ser normal usar muchos métodos de cada instancia y sería mejor usar objetos instanciados. Si queremos usar el objeto en, por ejemplo, diferentes controladores, podemos hacer un singleton tipo Objeto::getInstance y así nos aseguramos que sólo se instancia una vez en cada transacción. Además, podremos tener mock objects y testeos unitarios a cualquier nivel.

Siendo puristas, static debe estar en métodos que no dependen de la instancia, pero si pensamos en una clase de modelo de datos, que habitualmente instanciamos una vez y son funciones que acceden directamente a la BBDD, la verdad es que no está claro si cumple esta frase o no.

Personalmente, esperaba resultados similares a esto pero me ha sorprendido lo poco que empeora al poner 50 métodos y la poca diferencia de static a no-static.

En fin, espero que lo hayáis encontrado interesante ya que es un tema donde mucha gente opina pero casi nadie lo sabe con certeza.

You may also like...

6 Responses

  1. Toño says:

    Gracias por otro post interesante..

  2. Javier says:

    Sigue siendo útil, gracias por el post.

  3. Victor says:

    😮 buen post, gracias se ve muy interesante

  4. jcr says:

    hay una más rápida, prueba:

    $o = new testInstance1();

  5. jcr says:

    se cortó, decía q intentes declarar $o fuera del for() y luego en la bucle hace $o->f1();$o->f2 etc, y verás q es más rápido q los otros dos

  6. marcos says:

    Muy interesante!

Leave a Reply

Your email address will not be published. Required fields are marked *