19 de marzo de 2011

[PHP] Arrays pasados por parámetro ¿copias o referencias? (2/2)

conjuntoEntradas() {
  [PHP] Arrays pasados por parámetro ¿copias o referencias? (1/2)
  [PHP] Arrays pasados por parámetro ¿copias o referencias? (2/2)
}


El rendimiento

Hasta aquí podía apaliar una de mis inquietudes, que era el nivel de integridad de los atributos de un objeto para cada una de las maneras de tratar los parámetros de los métodos y su asignación a estos, pero todavía me quedaba otra pendiente, que era saber el rendimiento de cada una de estas formas.

Ya se que en el rendimiento de una aplicación están involucrados muchos factores y cuando tu escribes un código, el orden de ejecución de las instrucciones puede ser alterado por el compilador con el objetivo de optimizar el código para una ejecución más eficiente, no obstante siempre que programa y me encuentro delante de los ojos que el orden de ciertas instrucciones podría ser cambiado, a priori por una más optima, si no me aleja enormemente de lo que estoy haciendo, hago el refactoring, o sino me lo marco con @todo; ya sé que ha veces esto puede ser un poco de maniático o de paranoico, pero es algo intrínseco en mi personalidad junto con la concepción de que las pequeñas cosas forman el todo, por lo que si ese pequeño cambio puede llegar a optimizar una mil millonésima parte de la ejecución, lo aplico de todas formas, siempre pienso “mejor eso que nada”.

Bueno dejando de lado mis paranoias, os dejo la modificación del código de la prueba anterior de manera que me permitiera analizar la carga de ejecución de cada manera de pasar parámetros y de asignación de estos atributos.

class A {
    private $arrValor;
    private $arrValorToRef;
    private $arrReferencia;
    private $arrRefToRef;
    

    public function setArrayByValue(array $setArr) {
        $this->arrValor = $setArr;            
    }

    public function setArrayByValueToRef(array $setArr) {
        $this->arrValorToRef = &$setArr;            
    }

    public function setArrayByRef(array &$setArr) {
        $this->arrReferencia = $setArr;
    }

    public function setArrayByRefToRef(array &$setArr) {
        $this->arrRefToRef = &$setArr;        
    }

}


$numIt = 1000000;
$arr = array('key0' => 'value0');
$objA = new A();

for ($it = 0; $it < $numIt; $it++) {
    $objA->setArrayByValue($arr);
    $objA->setArrayByValueToRef($arr);
    $objA->setArrayByRef($arr); 
    $objA->setArrayByRefToRef($arr);
}

La modificación del script de prueba está clara, solo he dejé en los métodos de las clases declarada la asignación del parámetro al atributo, que es lo que quería comparar a nivel de coste de ejecución, y en la ejecución del script iterar unas cuantas vueltas dentro de un bucle la llamada a los cuatro métodos.

Para saber el coste de ejecución lo que hice es ejecutar el script en un profiler, concretamente el de xdebug; hice dos pruebas, una con 1.000.000 iteraciones y otra con 3.000.000 de iteraciones; ¿porqué tantas? para que el profiler pudiese medir con mayor precisión la diferencia entre cada llamada y ¿porqué dos veces?, para ver si los valores se obtenidos daban valores diferenciales similares entre las llamadas de los distintos métodos.

El resultado que obtuve fue el siguiente:

 Gráfico de llamadas a los métodos con 1.000.000 de iteraciones


Tabla de porcentaje de tiempo de carga sobre el total de llamada a los métodos con 1.000.000 de iteraciones

Gráfico de llamadas a los métodos con 3.000.000 de iteraciones

Tabla de porcentaje de tiempo de carga sobre el total de llamada a los métodos con 3.000.000 de iteraciones

Después de los resultados obtenidos a partir de los ficheros generados por el profiler con la ejecución del script, y haber sacada los gráficos y las tablas anteriores de cada ejecución con el visor kcachegrind, me decidí a poner los valores en un hoja de cálculo para poder analizar de manera más cómoda los resultados.

 Valores de los resultados, medias y diferencias entre ellos

Como podemos observar, y era de esperar, el método más eficiente es pasar el parámetro por referencia y asignar la referencia al atributo, ya que no se hace ninguna copia del array pasado por parámetro. Lo más curioso es que el siguiente más eficiente es el método que se pasa el parámetro por valor y se asigna por valor (setArrayByValue), estando por delante de los otros dos, yo, si no hubiese hecho esta prueba, hubiese intuido que éste sería el menos eficiente, ya que debería estar haciendo dos copias del array, una cuando lo pasa por parámetro y la otra cuando lo asigna a la variable, mientras que los otros dos deberían tener costes similares ya que hacen una copia del array.

Conclusiones

Después de lo visto tengo claro que para mantener la integridad y el encapsulamiento de los atributos de una clase en PHP, tengo que pasarlo por parámetro y asignarlos por parámetro, ya que si todo son referencias (parámetro y asignación al atributo), como ya suponía, los valores pueden ser modificados desde fuera; además después, de lo observado, también tengo claro que de nada sirve pasar el parámetro o asignarlo al atributo por referencia, ya que no introduce optimización ninguna, es más, el rendimiento disminuye, ya que la ejecución es más costosa.
Además de todo esto, me puedo despreocupar de que ciertas referencias queden apuntando espacios de memoria liberados, ya que el interprete garantiza la destrucción de los espacios de memoria, solo cuando no haya ninguna variable que pueda referenciar a éste.


Hasta la próxima enfermos.

No hay comentarios:

Publicar un comentario