【问题标题】:PHP 5 Reflection API performancePHP 5 反射 API 性能
【发布时间】:2008-11-16 23:41:58
【问题描述】:

我目前正在考虑在我自己的 MVC Web 框架中使用反射类(主要是反射类和反射方法),因为我需要自动实例化控制器类并调用它们的方法而无需任何配置(“约定优于配置”方法) .

我担心性能,尽管我认为数据库请求可能比实际的 PHP 代码成为更大的瓶颈。

所以,我想知道从性能的角度来看,是否有人对 PHP 5 反射有任何好的或不好的经验。

此外,我很想知道是否有任何流行的 PHP 框架(CI、Cake、Symfony 等)真正使用反射。

【问题讨论】:

  • 我在 Europa 中使用反射(europaphp.orggithub.com/treshugart/EuropaPHP),它比所有这些都快。
  • Zend Framework 1.x 在引导中使用反射。由于 ZF 允许自动调用 _init 函数,因此它需要某种机制来实现这一点。它使用反射。
  • Laravel 经常使用反射。

标签: php performance reflection


【解决方案1】:

我对这 3 个选项进行了基准测试(另一个基准测试没有拆分 CPU 周期并且已经 4 年了):

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(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 1000000));

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

还有相对时间,也有 1,000,000 次迭代(单独运行):

ph()->Dump(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 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 太棒了。你忘了提到 PHP 的版本。请注明。
  • 在 PHP 7.1.7 上测试:array(3) { ["directCall"]=&gt; string(10) "1.00000000" ["variableCall"]=&gt; string(10) "1.06057096" ["reflectedCall"]=&gt; string(10) "2.59103844" }
【解决方案2】:

别担心。安装Xdebug 并确定瓶颈在哪里。

使用反射是有成本的,但这是否重要取决于您在做什么。如果您使用反射实现控制器/请求调度程序,那么每个请求只使用一次。完全可以忽略不计。

如果您使用反射实现 ORM 层,将其用于每个对象甚至对属性的每次访问,并创建成百上千个对象,那么它的成本可能会很高。

【讨论】:

  • 谢谢,我不知道 Xdebug。它看起来像一个很棒的工具。我的 ORM 层根本不应该使用反射,我只会将它用于我的请求调度程序一次。我认为你对它可以忽略不计是对的!
【解决方案3】:

在我的机器上调用静态函数 100 万次将花费大约 0.31 秒。使用 ReflectionMethod 时,大约需要 1.82 秒。这意味着使用反射 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;

显然,实际影响取决于您希望拨打的电话次数

【讨论】:

  • 它可能会贵 500%,但平均每次调用仍然只有 1.82 微秒。
  • 这个测试不正确,因为反射方法的实例应该只创建一次。不在循环中。
【解决方案4】:

我想要更新的东西,所以看看this repo。摘自:

  • 在反射情况下,PHP 7 的速度几乎是 PHP 5 的两倍 - 这并不直接表明 PHP7 上的反射速度更快, PHP7 核心刚刚获得了很大的优化,所有代码都将 从中受益。
  • 基本反射非常快 - 读取 1000 个类的方法和文档集只需几毫秒。解析/自动加载 类文件确实比实际反射花费更多时间 力学。在我们的测试系统上,加载 1000 个类大约需要 300 毫秒 文件到内存中(需要/包含/自动加载) - 而不仅仅是 1-5 毫秒 使用反射解析(doc cmets、getMethods 等) 课程数量。
  • 结论:反射速度很快,在正常使用情况下,您可以忽略这种性能影响。但是,始终建议 只解析必要的。而且,缓存反射并没有给出 您对性能有任何明显的好处。

另外,请查看another benchmark

这些结果是在使用 PHP 的开发 OS X 机器上获得的 5.5.5。 [...]

  • 读取一个对象的单个属性:闭包速度稍快。

  • 读取多个对象的单个属性:反射速度更快。

  • 读取对象的所有属性:闭包更快。

  • 在一个对象上写入单个属性:反射稍快。

  • 在多个对象上写入单个属性:反射要快得多。

【讨论】:

    【解决方案5】:

    此外,我很想知道是否有 流行的 PHP 框架之一(CI, Cake、Symfony 等)实际使用 反射。

    http://framework.zend.com/manual/en/zend.server.reflection.html

    “通常,此功能仅由框架服务器类的开发人员使用。”

    【讨论】:

      【解决方案6】:

      开销很小,因此不会有很大的性能损失 其他东西,如 db、模板处理等都是性能问题,用一个简单的动作测试你的框架,看看它有多快。

      例如下面的代码(前端控制器)使用反射在几毫秒内完成工作

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

      【讨论】:

        【解决方案7】:

        在我的例子中,反射只比直接调用类方法慢 230%,与 call_user_func 函数一样快。

        【讨论】:

        • 在我的基准测试(我已经发布)中,ReflectionMethod 几乎call_user_func 一样快,尽管它比直接调用慢 200% - 220%方法。
        【解决方案8】:

        有时使用诸如 call_user_func_array() 之类的东西可以得到你需要的东西。不知道性能有何不同。

        【讨论】:

          【解决方案9】:

          CodeIgniter 肯定使用反射。我敢打赌其他人也会这样做。在 ci 安装中查看 system/controller 文件夹中的 Controller 类。

          【讨论】:

            【解决方案10】:

            基于@Alix Axel 提供的代码

            因此,为了完整起见,我决定将每个选项包装在一个类中,并在适用的情况下包括对象缓存。这是结果和代码 i7-4710HQ 上 PHP 5.6 的结果

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

            【讨论】:

              猜你喜欢
              • 2014-08-29
              • 2010-09-30
              • 2011-04-30
              • 2011-07-13
              • 1970-01-01
              • 2018-05-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多