2008-11-16 18 views
52

estoy considerando actualmente el uso de clases de reflexión (ReflectionClass y ReflectionMethod principalmente) en mi propio framework web MVC, porque necesitan clases de controlador para instanciar automáticamente e invocan sus métodos sin ninguna configuración requerida ("Convención sobre la configuración "enfoque).PHP 5 Reflexión de rendimiento API

estoy preocupado por el rendimiento, aunque creo que las solicitudes de base de datos es probable que sean los cuellos de botella más grande que el código PHP real.

Por lo tanto, me pregunto si alguien tiene alguna buena o mala experiencia con PHP 5 Reflexión desde el punto de vista del rendimiento.

Además, tengo curiosidad de saber si cualquiera de los marcos de PHP populares (CI, pastel, Symfony, etc.) en realidad utilizar la reflexión.

+0

que utilizar la reflexión en Europa (http://europaphp.org, http://github.com/treshugart/EuropaPHP) y es más rápido que todo eso. – Tres

+0

Zend Framework 1.x usa Reflection in Bootstrapping. Dado que ZF permite que las funciones '_init' se llamen automáticamente, se necesita algún tipo de mecanismo para esto. Y usa Reflection. –

+0

Laravel usa Reflection bastante. –

Respuesta

46

No se preocupe. Instale Xdebug y asegúrese de dónde está el cuello de botella.

No es el costo de utilizar la reflexión, pero si lo que importa depende de lo que está haciendo. Si implementa el controlador/despachador de solicitudes usando Reflection, entonces solo tiene un uso por solicitud. Absolutamente insignificante.

Si implementa la capa ORM utilizando la reflexión, la utiliza para cada objeto o incluso cada acceso a una propiedad, y crear cientos o miles de objetos, entonces podría ser costoso.

+1

Gracias, no sabía sobre Xdebug. Parece una gran herramienta. Mi capa ORM no debe utilizar la reflexión a todos, voy a utilizar sólo una vez para mi solicitud despachador. ¡Creo que tienes razón sobre que es insignificante! – Franck

4

La sobrecarga es pequeño, así que no es una gran pérdida de rendimiento otras cosas como db, el procesamiento de la plantilla, etc., son los problemas de rendimiento, prueba de su marco con una acción simple para ver lo rápido que es.

Por ejemplo, el código de abajo (FrontController), que utiliza la reflexión lo hace trabajos en unos pocos milisegundos

<?php 
require_once('sanitize.inc'); 

/** 
* MVC Controller 
* 
* This Class implements MVC Controller part 
* 
* @package MVC 
* @subpackage Controller 
* 
*/ 
class Controller { 

    /** 
    * Standard Controller constructor 
    */ 
    static private $moduleName; 
    static private $actionName; 
    static private $params; 

    /** 
    * Don't allow construction of the controller (this is a singleton) 
    * 
    */ 
    private function __construct() { 

    } 

    /** 
    * Don't allow cloning of the controller (this is a singleton) 
    * 
    */ 
    private function __clone() { 

    } 

    /** 
    * Returns current module name 
    * 
    * @return string 
    */ 
    function getModuleName() { 
     return self :: $moduleName; 
    } 

    /** 
    * Returns current module name 
    * 
    * @return string 
    */ 
    function getActionName() { 
     return self :: $actionName; 
    } 

    /** 
    * Returns the subdomain of the request 
    * 
    * @return string 
    */ 
    function getSubdomain() { 
     return substr($_SERVER['HTTP_HOST'], 0, strpos($_SERVER['HTTP_HOST'], '.')); 
    } 

    function getParameters($moduleName = false, $actionName = false) { 
     if ($moduleName === false or ($moduleName === self :: $moduleName and $actionName === self :: $actionName)) { 
      return self :: $params; 
     } else { 
      if ($actionName === false) { 
       return false; 
      } else { 
       @include_once (FRAMEWORK_PATH . '/modules/' . $moduleName . '.php'); 
       $method = new ReflectionMethod('mod_' . $moduleName, $actionName); 
       foreach ($method->getParameters() as $parameter) { 
        $parameters[$parameter->getName()] = null; 
       } 
       return $parameters; 
      } 
     } 
    } 

    /** 
    * Redirect or direct to a action or default module action and parameters 
    * it has the ability to http redirect to the specified action 
    * internally used to direct to action 
    * 
    * @param string $moduleName 
    * @param string $actionName 
    * @param array $parameters 
    * @param bool $http_redirect 

    * @return bool 
    */ 
    function redirect($moduleName, $actionName, $parameters = null, $http_redirect = false) { 
     self :: $moduleName = $moduleName; 
     self :: $actionName = $actionName; 
     // We assume all will be ok 
     $ok = true; 

     @include_once (PATH . '/modules/' . $moduleName . '.php'); 

     // We check if the module's class really exists 
     if (!class_exists('mod_' . $moduleName, false)) { // if the module does not exist route to module main 
      @include_once (PATH . '/modules/main.php'); 
      $modClassName = 'mod_main'; 
      $module = new $modClassName(); 
      if (method_exists($module, $moduleName)) { 
       self :: $moduleName = 'main'; 
       self :: $actionName = $moduleName; 
       //$_PARAMS = explode('/' , $_SERVER['REQUEST_URI']); 
       //unset($parameters[0]); 
       //$parameters = array_slice($_PARAMS, 1, -1); 
       $parameters = array_merge(array($actionName), $parameters); //add first parameter 
      } else { 
       $parameters = array($moduleName, $actionName) + $parameters; 
       $actionName = 'index'; 
       $moduleName = 'main'; 
       self :: $moduleName = $moduleName; 
       self :: $actionName = $actionName; 
      } 
     } else { //if the action does not exist route to action index 
      @include_once (PATH . '/modules/' . $moduleName . '.php'); 
      $modClassName = 'mod_' . $moduleName; 
      $module = new $modClassName(); 
      if (!method_exists($module, $actionName)) { 
       $parameters = array_merge(array($actionName), $parameters); //add first parameter 
       $actionName = 'index'; 
      } 
      self :: $moduleName = $moduleName; 
      self :: $actionName = $actionName; 
     } 
     if (empty($module)) { 
      $modClassName = 'mod_' . self :: $moduleName; 
      $module = new $modClassName(); 
     } 

     $method = new ReflectionMethod('mod_' . self :: $moduleName, self :: $actionName); 

     //sanitize and set method variables 
     if (is_array($parameters)) { 
      foreach ($method->getParameters() as $parameter) { 
       $param = current($parameters); 
       next($parameters); 
       if ($parameter->isDefaultValueAvailable()) { 
        if ($param !== false) { 
         self :: $params[$parameter->getName()] = sanitizeOne(urldecode(trim($param)), $parameter->getDefaultValue()); 
        } else { 
         self :: $params[$parameter->getName()] = null; 
        } 
       } else { 
        if ($param !== false) {//check if variable is set, avoid notice 
         self :: $params[$parameter->getName()] = sanitizeOne(urldecode(trim($param)), 'str'); 
        } else { 
         self :: $params[$parameter->getName()] = null; 
        } 
       } 
      } 
     } else { 
      foreach ($method->getParameters() as $parameter) { 
       self :: $params[$parameter->getName()] = null; 
      } 
     } 

     if ($http_redirect === false) {//no redirecting just call the action 
      if (is_array(self :: $params)) { 
       $method->invokeArgs($module, self :: $params); 
      } else { 
       $method->invoke($module); 
      } 
     } else { 
      //generate the link to action 
      if (is_array($parameters)) { // pass parameters 
       $link = '/' . $moduleName . '/' . $actionName . '/' . implode('/', self :: $params); 
      } else { 
       $link = '/' . $moduleName . '/' . $actionName; 
      } 
      //redirect browser 
      header('Location:' . $link); 

      //if the browser does not support redirecting then provide a link to the action 
      die('Your browser does not support redirect please click here <a href="' . $link . '">' . $link . '</a>'); 
     } 
     return $ok; 
    } 

    /** 
    * Redirects to action contained within current module 
    */ 
    function redirectAction($actionName, $parameters) { 
     self :: $actionName = $actionName; 
     call_user_func_array(array(&$this, $actionName), $parameters); 
    } 

    public function module($moduleName) { 
     self :: redirect($moduleName, $actionName, $parameters, $http_redirect = false); 
    } 

    /** 
    * Processes the client's REQUEST_URI and handles module loading/unloading and action calling 
    * 
    * @return bool 
    */ 
    public function dispatch() { 
     if ($_SERVER['REQUEST_URI'][strlen($_SERVER['REQUEST_URI']) - 1] !== '/') { 
      $_SERVER['REQUEST_URI'] .= '/'; //add end slash for safety (if missing) 
     } 

     //$_SERVER['REQUEST_URI'] = @str_replace(BASE ,'', $_SERVER['REQUEST_URI']); 
     // We divide the request into 'module' and 'action' and save paramaters into $_PARAMS 
     if ($_SERVER['REQUEST_URI'] != '/') { 
      $_PARAMS = explode('/', $_SERVER['REQUEST_URI']); 

      $moduleName = $_PARAMS[1]; //get module name 
      $actionName = $_PARAMS[2]; //get action 
      unset($_PARAMS[count($_PARAMS) - 1]); //delete last 
      unset($_PARAMS[0]); 
      unset($_PARAMS[1]); 
      unset($_PARAMS[2]); 
     } else { 
      $_PARAMS = null; 
     } 

     if (empty($actionName)) { 
      $actionName = 'index'; //use default index action 
     } 

     if (empty($moduleName)) { 
      $moduleName = 'main'; //use default main module 
     } 
     /* if (isset($_PARAMS)) 

      { 

      $_PARAMS = array_slice($_PARAMS, 3, -1);//delete action and module from array and pass only parameters 

      } */ 
     return self :: redirect($moduleName, $actionName, $_PARAMS); 
    } 
} 
+0

Tanto espacio en blanco hace que sea difícil seguir su código. –

18

Llamar a una función estática 1 millón de veces costará ~ 0.31 segundos en mi máquina. Cuando se utiliza un ReflectionMethod, cuesta ~ 1,82 segundos. Eso significa que es ~ 500% más caro usar la Reflection API.

Este es el código que utilicé por cierto:

<?PHP 

class test 
{ 
    static function f(){ 
      return; 
    } 
} 

$s = microtime(true); 
for ($i=0; $i<1000000; $i++) 
{ 
    test::f('x'); 
} 
echo ($a=microtime(true) - $s)."\n"; 

$s = microtime(true); 
for ($i=0; $i<1000000; $i++) 
{ 
    $rm = new ReflectionMethod('test', 'f'); 
    $rm->invokeArgs(null, array('f')); 
} 

echo ($b=microtime(true) - $s)."\n"; 

echo 100/$a*$b; 

Obviamente, el impacto real depende de la cantidad de llamadas que se pueden esperar hacer

+17

Puede ser 500% más costoso, pero todavía promedia a solo 1,82 microsegundos por llamada. –

+4

Esta prueba es incorrecta porque la instancia del método de reflexión debe crearse solo una vez. No en ciclo – lisachenko

+0

@Alexander: compruebe mi punto de referencia. –

1

CodeIgniter utiliza defenitly Reflexiones. Y apuesto a que los otros también lo hacen. Mire en la clase de Controlador en la carpeta del sistema/controlador en la instalación de ci.

2

En mi caso la reflexión que no es más que un 230% más lento que llamar a método de clase directamente, lo que tan rápido como función call_user_func.

+0

En mi punto de referencia (He publicado que) la ReflectionMethod es * casi * tan rápido como 'call_user_func', aunque sólo entre 200% - 220% más lento que el método de llamada directa. –

2

veces usando algo como call_user_func_array() se puede obtener lo que necesita.No sé cómo difiere el rendimiento.

52

I Benchmarked estas 3 opciones (el otro punto de referencia no era división ciclos de CPU y era 4y de edad):

class foo { 
    public static function bar() { 
     return __METHOD__; 
    } 
} 

function directCall() { 
    return foo::bar($_SERVER['REQUEST_TIME']); 
} 

function variableCall() { 
    return call_user_func(array('foo', 'bar'), $_SERVER['REQUEST_TIME']); 
} 

function reflectedCall() { 
    return (new ReflectionMethod('foo', 'bar'))->invoke(null, $_SERVER['REQUEST_TIME']); 
} 

El tiempo absoluto tomado por 1.000.000 de iteraciones:

print_r (punto de referencia (array ('directCall', 'variableCall', 'reflectedCall'), 1000000));

Array 
(
    [directCall] => 4.13348770 
    [variableCall] => 6.82747173 
    [reflectedCall] => 8.67534351 
) 

Y el tiempo relativo, también con 1.000.000 iteraciones (run separada):

ph() -> Dump (punto de referencia (array ('DIRECTCALL', 'variableCall', ' reflectCall '), 1000000, verdadero));

Array 
(
    [directCall] => 1.00000000 
    [variableCall] => 1.67164707 
    [reflectedCall] => 2.13174915 
) 

Parece que el rendimiento se incrementó en gran medida la reflexión en 5.4.7 (de ~ 500% al 213% ~ ).

Aquí está la función Benchmark() Solía ​​si alguien quiere volver a ejecutar este punto de referencia:

function Benchmark($callbacks, $iterations = 100, $relative = false) 
{ 
    set_time_limit(0); 

    if (count($callbacks = array_filter((array) $callbacks, 'is_callable')) > 0) 
    { 
     $result = array_fill_keys($callbacks, 0); 
     $arguments = array_slice(func_get_args(), 3); 

     for ($i = 0; $i < $iterations; ++$i) 
     { 
      foreach ($result as $key => $value) 
      { 
       $value = microtime(true); 
       call_user_func_array($key, $arguments); 
       $result[$key] += microtime(true) - $value; 
      } 
     } 

     asort($result, SORT_NUMERIC); 

     foreach (array_reverse($result) as $key => $value) 
     { 
      if ($relative === true) 
      { 
       $value /= reset($result); 
      } 

      $result[$key] = number_format($value, 8, '.', ''); 
     } 

     return $result; 
    } 

    return false; 
} 
+1

+1 para la respuesta elocuente y los detalles proporcionados. – onalbi

+1

+1 por ser increíble. Olvidó mencionar la versión de PHP. Por favor denote eso. –

+0

probado en PHP 7.1.7: 'array (3) { [ "DirectCall"] => string (10) "1,00000000" [ "variableCall"] => string (10) "1,06057096" [" reflectedCall "] => cadena (10)" 2.59103844 " }' – Ryall

1

basado en el código que @Alix Axel proporcionó

Así que para la integridad decidí envolver cada opción una clase e incluye almacenamiento en caché de los objetos donde corresponda. aquí fue el resultado y el código Los resultados en PHP 5.6 en un i7-4710HQ Código

array (
    'Direct' => '5.18932366', 
    'Variable' => '5.62969398', 
    'Reflective' => '6.59285069', 
    'User' => '7.40568614', 
) 

:

function Benchmark($callbacks, $iterations = 100, $relative = false) 
{ 
    set_time_limit(0); 

    if (count($callbacks = array_filter((array) $callbacks, 'is_callable')) > 0) 
    { 
     $result = array_fill_keys(array_keys($callbacks), 0); 
     $arguments = array_slice(func_get_args(), 3); 

     for ($i = 0; $i < $iterations; ++$i) 
     { 
      foreach ($result as $key => $value) 
      { 
       $value = microtime(true); call_user_func_array($callbacks[$key], $arguments); $result[$key] += microtime(true) - $value; 
      } 
     } 

     asort($result, SORT_NUMERIC); 

     foreach (array_reverse($result) as $key => $value) 
     { 
      if ($relative === true) 
      { 
       $value /= reset($result); 
      } 

      $result[$key] = number_format($value, 8, '.', ''); 
     } 

     return $result; 
    } 

    return false; 
} 

class foo { 
    public static function bar() { 
     return __METHOD__; 
    } 
} 

class TesterDirect { 
    public function test() { 
     return foo::bar($_SERVER['REQUEST_TIME']); 
    } 
} 

class TesterVariable { 
    private $class = 'foo'; 

    public function test() { 
     $class = $this->class; 

     return $class::bar($_SERVER['REQUEST_TIME']); 
    } 
} 

class TesterUser { 
    private $method = array('foo', 'bar'); 

    public function test() { 
     return call_user_func($this->method, $_SERVER['REQUEST_TIME']); 
    } 
} 

class TesterReflective { 
    private $class = 'foo'; 
    private $reflectionMethod; 

    public function __construct() { 
     $this->reflectionMethod = new ReflectionMethod($this->class, 'bar'); 
    } 

    public function test() { 
     return $this->reflectionMethod->invoke(null, $_SERVER['REQUEST_TIME']); 
    } 
} 

$testerDirect = new TesterDirect(); 
$testerVariable = new TesterVariable(); 
$testerUser = new TesterUser(); 
$testerReflective = new TesterReflective(); 

fputs(STDOUT, var_export(Benchmark(array(
    'Direct' => array($testerDirect, 'test'), 
    'Variable' => array($testerVariable, 'test'), 
    'User' => array($testerUser, 'test'), 
    'Reflective' => array($testerReflective, 'test') 
), 10000000), true)); 
0

quería algo nuevo, así echar un vistazo a this repo. Del resumen:

  • PHP 7 es casi dos veces más rápido que PHP 5 en el caso de las reflexiones - Esto no indica directamente que las reflexiones son más rápidos en PHP7, la PHP7 núcleo acaban de recibir una gran optimización y todo el código se se beneficiará de esto.
  • Los reflejos básicos son bastante rápidos: los métodos de lectura y los comentarios de doc para 1000 clases cuestan solo unos pocos milisegundos. El análisis/carga automática de los archivos de clase lleva mucho más tiempo que la mecánica de reflexión real . En nuestro sistema de pruebas, se necesitan aproximadamente 300 ms para cargar 1000 archivos de clase en la memoria (requiere/incluye/carga automática) - Y solo de 1-5 ms a usa el análisis de reflexión (comentarios de doc, getMethods, etc ...) en la misma cantidad de clases
  • Conclusión: los reflejos son rápidos y en casos de uso normales puede ignorar el impacto en el rendimiento. Sin embargo, siempre se recomienda solo analizar lo que sea necesario. Y, los reflejos de almacenamiento en caché no le dan ningún beneficio notable en el rendimiento.

Además, la salida another benchmark.

Esos resultados se obtuvieron en una máquina OS X de desarrollo utilizando PHP 5.5.5. [...]

  • Leer una sola propiedad en un objeto: El cierre es un poco más rápido.

  • Leer una sola propiedad en muchos objetos: La reflexión es mucho más rápido.

  • lectura todas las propiedades de un objeto: El cierre es más rápido.

  • Escritura de una sola propiedad en un objeto: La reflexión es un poco más rápido.

  • Escritura de una sola propiedad en muchos objetos: La reflexión es mucho más rápido.