Benchmarking de funciones

1 01UTC Agosto 01UTCViernes 2008 at 4:48 am (PHP)

Hola, en muchos blogs en los que se habla de optimizacion en el codigo php puede leerse que la funcion include es mas rapida que require y que include_once.
Asi mismo tambien se sabe que incluir un archivo con su ruta completa es mas rapido que incluir un archivo sin su ruta correspondiente. A continuacion voy a comentar como se llega a estas conclusiones por medio de una herramienta de benchmark, aunque no es necesario hacer un benchmark para saber todo esto, basta con leer el manual de php y saber algo de sistemas operativos ;)

1) Primero les copiare el objeto Benckmark, que es el que voy a usar para los testeo.

<?php

/**
* Benchmarking for PHP applications
*
* Benchmark class provides an easy way to meassure the performance
* of different parts of your PHP applications. You can get any
* counters as you want in the same page execution, and get the output
* via html output or file.
*
*  LICENCE
*  ========
*	copyright (c) 2000 Patxi Echarte [patxi@eslomas.com]
*
*	This program is free software; you can redistribute it and/or
*	modify it under the terms of the GNU Lesser General Public License
*	version 2.1 as published by the Free Software Foundation.
*
*	This library is distributed in the hope that it will be useful,
*	but WITHOUT ANY WARRANTY; without even the implied warranty of
*	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*	GNU Lesser General Public License for more details at
*	http://www.gnu.org/copyleft/lgpl.html
*
*	You should have received a copy of the GNU General Public License
*	along with this program; if not, write to the Free Software
*	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
* @package Benchmark
* @category Benchmarking
* @version $Id: Benchmark.class.php,v 1.2 2005/06/07 $
* @author Patxi Echarte <patxi@eslomas.com>
*/

class Benchmark{
	var $__start_times;  //contendrá los tiempos de inicio para cada contador
	var $__stop_times;
	var $__delta_points;

	/**
	* Constructor
    *
    * @access public
    */
   function Benchmark(){
      $this->_start_times = array();
      $this->_stop_times = array();
      $this->_delta_points = array();
   }

    /**
    * Inicializa un contador interno de benchmarking. Se le puede asignar un nombre
	* específico para llevar diferentes contadores en la misma petición
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @access public
    */
	function timingStart ($name = 'default') {
		$this->_start_times[$name] = explode(' ', microtime());
	}

    /**
    * Detiene un contador de benchmarking.
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @access public
    */
	function timingStop ($name = 'default') {
		$this->_stop_times[$name] = explode(' ', microtime());
	}

    /**
    * Devuelve el tiempo transcurrido para el contador que se indique, hasta
	* el momento actual si no se ha detenido ya el contador, o hasta el
	* momento en el que se detuvo en contador
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @return int número de milisegundos transcurridos
    * @access public
    */
	function timingCurrent ($name = 'default') {
		if (!isset($this->_start_times[$name])) {
			return 0;
		}
		if (!isset($this->_stop_times[$name])) {
			$stop_time = explode(' ', microtime());
		}
		else {
			$stop_time = $this->_stop_times[$name];
		}
		// do the big numbers first so the small ones aren't lost
		$current = $stop_time[1] - $this->_start_times[$name][1];
		$current += $stop_time[0] - $this->_start_times[$name][0];
		return $current;
	}

    /**
    * Añade un punto intermedio en el contador, de forma que el contador
	* continúa activo y cuando lo visualicemos obtengamos su información
	* y la de todos sus puntos intermedios
    *
    * @param string $txt identificador de texto para el punto, por defecto ''
    * @param string $name nombre del contador, por defecto 'default'
    * @access public
    */
	function addDeltaPoint($txt='',$name='default'){
		if(!is_array($this->_delta_points[$name]))
			$this->_delta_points[$name] = array();
		$this->_delta_points[$name][] = array('texto' => $txt, 'time' => explode(' ',microtime()));
	}

    /**
    * Devuelve un array con el contenido de todos los puntos intermedios de
	* ejecución del contador que se indique. Su formato es:
	*		array(texto=>nombre del punto, time=> milisegundos).
	* De esta forma obtenemos para un contador lo que le ha costado llegar a cada
	* punto intermedio desde el anterior, teniendo una imagen clara de que tareas
	* han costado más y cuales menos.
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @return array con los puntos y los milisegundos que ha costado llegar a ellos
    * @access public
    */
	function getDeltaPoints($name='default'){

		if(!is_array($this->_delta_points[$name])) return;

		$ini = array($this->_start_times[$name][0],$this->_start_times[$name][1]);

		foreach($this->_delta_points[$name] as $id => $point){

			$delta = $point['time'][1] - $ini[1];
			$delta += $point['time'][0] - $ini[0];

			$result[] = array(texto => $point['texto'], 'time' => $delta);

			$ini = $point['time'];

		}
		return $result;
	}

    /**
    * Devuelve una tabla HTML con el contenido de todos los puntos intermedios
	* para el contador indicado.
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @return string con el contenido de la tabla html
    * @access public
    */
	function getDeltaPointsHtmlTable($name='default'){

		if(!is_array($this->_delta_points[$name])) return;
		$ini = array($this->_start_times[$name][0],$this->_start_times[$name][1]);

		$res = '';
		$res = '<table border="1">';

		foreach($this->_delta_points[$name] as $id => $point){

			$delta = $point['time'][1] - $ini[1];
			$delta += $point['time'][0] - $ini[0];

			$res .= "<tr><td>$point[texto]</td><td>$delta</td></tr>";

			$ini = $point['time'];

		}
		$res .= "<tr><td><b>Total</b></td><td>" . $this->timingCurrent($name) . "</td></tr></table>";

		return $res;
	}

    /**
    * Escribe en el archivo que se indique una nueva línea al final del archivo,
	* con el contenido de cada punto intermedio, en forma de incrementos sobre
	* el punto anterior. Cada valor se escribe separado por comas para poder ser
	* leido fácilmente con CSV
    *
    * @param string $name nombre del contador, por defecto 'default'
    * @param string $file nombre del archivo a escribir, por defecto '/tmp/phpbenchmarck.log'
    * @access public
    */
	function saveDeltaPointsToFile($name='default',$file='/tmp/phpbenchmarck.log'){

		if(!is_array($this->_delta_points[$name])) return;
		$ini = array($this->_start_times[$name][0],$this->_start_times[$name][1]);

		foreach($this->_delta_points[$name] as $id => $point){
			 $delta = $point['time'][1] - $ini[1];
			 $delta += $point['time'][0] - $ini[0];

			 $result[] =  number_format($delta,8,",",".");

			 $ini = $point['time'];
		}
		$line = implode(';',$result);

		//abro el fichero y escribo la línea con LOCK!!!
		$fp = @fopen($file, 'a');
		@flock($fp, LOCK_EX);
		@fputs($fp,$line."\n");
		@flock($fp, LOCK_UN);
	}
}
?>

2) Bien, ahora voy a crear una carpeta llamada includes, donde voy a meter archivos del tipo unNumero.php, y cada archivo contendra 3 asignaciones y una operacion de suma.
Yo voy a meter uno 100 archivos, con la siguiente scritp, ustedes en sus casa pueden usar el bloc de notas, jajaja

for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
}

3) Testeando el tiempo de respuesta, ahora necesitamos crear 3 archivos;
require.php: este se encargara de usar la funcion require

<?php
include('Benchmark.class.php');
$bench=new Benchmark();

$bench->timingStart('test');
escribir();
$mInicio=memory_get_usage();
for ($i=0;$i<99;$i++){
	require('includes/'.$i.'.php');
}
$mFin=memory_get_usage();
$bench->addDeltaPoint('fin de test require', 'test');
echo 'requiere uso: '.($mFin-$mInicio).' bytes<br>';
$bench->saveDeltaPointsToFile('test','require.log');
$bench->timingStop();

function escribir(){
   for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
   }
}
?>

Luego el archivo inlcude_once.php que testeara la funcion include_once;

<?php
include('Benchmark.class.php');
$bench=new Benchmark();

$bench->timingStart('test');
escribir();
$mInicio=memory_get_usage();
for ($i=0;$i<99;$i++){
	include_once('includes/'.$i.'.php');
}
$mFin=memory_get_usage();
$bench->addDeltaPoint('fin de test include_once', 'test');
echo 'include_once uso: '.($mFin-$mInicio).' bytes<br>';
$bench->saveDeltaPointsToFile('test','include_once.log');
$bench->timingStop();

function escribir(){
   for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
   }
}
?>

Y por ultimo include.php que testeara la funcion include;

<?php
include('Benchmark.class.php');
$bench=new Benchmark();

$bench->timingStart('test');
escribir();
$mInicio=memory_get_usage();
for ($i=0;$i<99;$i++){
	include('includes/'.$i.'.php');
}
$mFin=memory_get_usage();
$bench->addDeltaPoint('fin de test include', 'test');
echo 'include uso: '.($mFin-$mInicio).' bytes<br>';
$bench->saveDeltaPointsToFile('test','include.log');
$bench->timingStop();

function escribir(){
   for ($i=0;$i<99;$i++){
	$caracteres=array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o');
	shuffle($caracteres);
	$var1='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var2='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);
	$var3='$'.array_shift($caracteres).array_shift($caracteres).array_shift($caracteres);

	$out='<? '.$var1.'='.rand().'; '.$var2.'='.rand().'; '.$var3.'='.$var1.'+'.$var2.'; ?>';

	$file=fopen('includes/'.$i.'.php','w');
	fwrite($file,$out);
	fclose($file);
   }
}
?>

Bien, espero que hagan las pruebas y veran que el include es el mas rapido y menos costoso, ademas si agregamos la ruta completa, cosa que yo no hice en los ejemplos, veran que el tiempo de carga es todavia menor.

Saludos, Eugenio

Escribe un comentario