2008-11-16 77 views
52

我目前正在考慮在我自己的MVC Web框架中使用Reflection類(ReflectionClass和ReflectionMethod主要),因爲我需要自動實例化控制器類並調用它們的方法而不進行任何必需的配置(「約定過度配置「方法)。PHP 5反射API性能

即使我認爲數據庫請求可能會比實際的PHP代碼有更大的瓶頸,但我擔心性能。

所以,我想知道如果任何人從性能的角度來看PHP 5反射有任何好的或不好的經驗。

此外,我很想知道是否有流行的PHP框架(CI,Cake,Symfony等)實際上使用了Reflection。

+0

我用在歐羅巴反射(http://europaphp.org,http://github.com/treshugart/EuropaPHP),它比那些快。 – Tres 2011-03-16 05:56:21

+0

Zend Framework 1.x在引導中使用反射。由於ZF允許自動調用`_init`函數,因此它需要某種機制。它使用反射。 – 2012-02-12 21:16:12

+0

Laravel使用反射相當多。 – 2014-09-04 02:55:18

回答

46

不要擔心。安裝Xdebug並確保瓶頸在哪裏。

使用反射有成本,但是否重要取決於你在做什麼。如果您使用Reflection實現控制器/請求調度程序,那麼它只是每個請求的一個用法。絕對可以忽略不計。

如果您使用反射來實現您的ORM層,請將其用於每個對象,甚至是每次訪問某個屬性,並創建數百或數千個對象,那麼這可能代價高昂。

+1

謝謝,我不知道Xdebug。它看起來像一個偉大的工具。 我的ORM層不應該使用反射,我只會在我的請求調度程序中使用它。我認爲你是對的可以忽略不計! – Franck 2008-11-17 00:01:00

4

開銷很小,所以沒有很大的性能損失其他的東西像db,模板處理等都是性能問題,用一個簡單的動作來測試你的框架,看看它有多快。

例如,它使用反射的代碼波紋管(frontcontroller)做它工作在幾毫秒

<?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

這麼大的空白空間讓你難以遵循你的代碼。 – 2013-02-01 19:48:47

18

調用靜態函數100萬次將在我的機器上花費約0.31秒。使用ReflectionMethod時,耗資約1.82秒。這意味着它使用Reflection API的成本要高500%。

這是我使用的方式代碼:

<?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; 

顯然,實際的影響取決於你希望做

+17

價格可能會高出500%,但平均每次通話只需1.82微秒。 – 2009-10-06 09:23:38

1

笨defenitly使用反思的呼叫數量。我敢打賭其他人也這樣做。查看ci安裝中system/controller文件夾中的Controller類。

2

在我的情況下,反射比直接調用類方法慢了230%,這與call_user_func函數一樣快。

2

有時候使用類似call_user_func_array()的東西可以得到你所需要的東西。不知道如何表現不同。

52

我這些基準3個選項(其他基準不是分裂的CPU週期,是4Y歲):

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']); 
} 

採取1,000,000迭代的絕對時間:

的print_r(基準(數組('directCall','variableCall', 'reflectedCall'),1000000));

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

和相對時間,也與百萬迭代(單獨的運行):

pH值() - >轉儲(基準(陣列( 'directCall', 'variableCall', ' reflectCall'),1000000,true));

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

看來,反射性能是在5.4.7大大增加(從500〜%下降到〜213%)。

這裏是我以前如果有人想重新運行這個測試的Benchmark()功能:

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

基於該@Alix阿克塞爾提供

代碼,以便爲完整性我決定來包裝各個選項一個類,並在適用的地方包含緩存對象。這裏是上i7-4710HQ

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

碼結果和代碼 在PHP 5.6的結果:

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

我想要的東西更新,所以看看this repo。從彙總:

  • PHP 7幾乎快兩倍,PHP 5中反射的情況下 - 這並不直接表明,反射是在PHP7快, PHP7核心剛剛收到了巨大的優化和所有的代碼將從 受益。
  • 的基本思路是相當快 - 1000類閱讀方法和文檔註釋成本只有幾毫秒。解析/自動加載的類文件 確實需要更多的時間比實際反映 機制。在我們testsystem需要300ms左右,以1000個 文件加載到內存中(需要/包括/自動加載) - 而不僅僅是1-5ms到 使用反射在同一 量分析(doc註釋,的getMethods,等...)的類。
  • 結論:思考速度快且在正常使用情況下,您可以忽略性能的影響。然而,總是建議 只解析什麼是必要的。而且,緩存反射並不會給你帶來任何顯着的性能優勢。

此外,請檢查出another benchmark

這些結果是在使用PHP 5.5.5的開發OS X機器上獲得的。 [...]

  • 在一個對象上讀取一個屬性:關閉速度稍快。

  • 在許多對象上讀取單個屬性:反射速度更快。

  • 讀取對象的所有屬性:關閉更快。

  • 在一個對象上寫入單個屬性:反射速度稍快。

  • 在許多對象上編寫單個屬性:反射速度更快。