代码之家  ›  专栏  ›  技术社区  ›  Franck

PHP5反射API性能

  •  55
  • Franck  · 技术社区  · 17 年前

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

    所以,我想知道是否有人从性能的角度对PHP5反射有任何好的或坏的经验。

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

    10 回复  |  直到 13 年前
        1
  •  58
  •   DisgruntledGoat    11 年前

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

    1000000次迭代所用的绝对时间:

    打印(基准(数组('directCall','variableCall',', “反射呼叫”),1000000);

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

    ph()->转储(基准(数组('directCall','variableCall',

    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;
    }
    
        2
  •  57
  •   Kornel    17 年前

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

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

        3
  •  18
  •       17 年前

    这是我用的代码:

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

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

        4
  •  5
  •   vartec    17 年前
        5
  •  5
  •   XedinUnknown    8 年前

    我想要一些更新的,所以看看 this repo

    • 在反射的情况下,PHP7的速度几乎是PHP5的两倍——这并不直接表明PHP7上的反射速度更快 从中受益。
    • 文件进入内存(需要/包含/自动加载)-并且仅需1-5毫秒即可 在同一平台上使用反射解析(doc注释、getMethods等) 课程数量。
    • 结论:反射速度很快,在正常用例中,您可以忽略性能影响。但是,始终建议 只解析必要的内容。而且,缓存反射不会提供

    another benchmark .

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

    • 读取多个对象上的单个属性:反射要快得多。

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

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

        6
  •  4
  •   code-jaff    13 年前

    开销很小,所以不会有太大的性能损失。其他东西,如数据库、模板处理等,都是性能问题,用一个简单的操作来测试你的框架,看看它有多快。

    例如,使用反射的代码bellow(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);
        }
    }
    
        7
  •  2
  •   Lu4    15 年前

        8
  •  2
  •   grantwparks    15 年前

    有时,使用call\u user\u func\u array()之类的方法可以满足您的需要。不知道性能有什么不同。

        9
  •  1
  •   Dre    16 年前

    CodeIgniter防御性地使用反射。我打赌其他人也会。查看ci安装中system/Controller文件夹中的控制器类。

        10
  •  1
  •   WiR3D    11 年前

    基于@Alix Axel提供的代码

    因此,为了完整性,我决定将每个选项包装在一个类中,并在适用的地方包括对象缓存。下面是结果和代码

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