PHP 5 反射 API 性能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/294582/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 22:16:12  来源:igfitidea点击:

PHP 5 Reflection API performance

phpperformancereflection

提问by Franck

I'm currently considering the use of Reflection classes (ReflectionClass and ReflectionMethod mainly) in my own MVC web framework, because I need to automatically instanciate controller classes and invoke their methods without any required configuration ("convention over configuration" approach).

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

I'm concerned about performance, even though I think that database requests are likely to be bigger bottlenecks than the actual PHP code.

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

So, I'm wondering if anyone has any good or bad experience with PHP 5 Reflection from a performance point of view.

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

Besides, I'd be curious to know if any one of the popular PHP frameworks (CI, Cake, Symfony, etc.) actually use Reflection.

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

采纳答案by Kornel

Don't be concerned. Install Xdebugand be sure where the bottleneck is.

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

There is cost to using reflection, but whether that matters depends on what you're doing. If you implement controller/request dispatcher using Reflection, then it's just one use per request. Absolutely negligible.

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

If you implement your ORM layer using reflection, use it for every object or even every access to a property, and create hundreds or thousands objects, then it might be costly.

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

回答by Alix Axel

I benchmarked these 3 options (the other benchmark wasn't splitting CPU cycles and was 4y old):

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

The absolute time taken for 1,000,000 iterations:

1,000,000 次迭代所用的绝对时间:

print_r(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 1000000));

print_r(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 1000000));

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

And the relative time, also with 1,000,000 iterations (separate run):

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

ph()->Dump(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 1000000, true));

ph()->Dump(Benchmark(array('directCall', 'variableCall', 'reflectedCall'), 1000000, true));

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

It seems that the reflection performance was greatly increased in 5.4.7 (from ~500% down to ~213%).

似乎反射性能在 5.4.7 中大大提高(从 ~500% 下降到~213%)。

Here's the Benchmark()function I used if anyone wants to re-run this benchmark:

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

回答by Kornel

Calling a static function 1 million times will cost ~ 0.31 seconds on my machine. When using a ReflectionMethod, it costs ~ 1.82 seconds. That means it is ~ 500% more expensive to use the Reflection API.

在我的机器上调用静态函数 100 万次将花费约 0.31 秒。使用 ReflectionMethod 时,它花费约 1.82 秒。这意味着使用反射 API 的成本要高出约 500%。

This is the code I used by the way:

这是我顺便使用的代码:

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

Obviously, the actual impact depends on the number of calls you expect to do

显然,实际影响取决于您期望进行的调用次数

回答by vartec

Besides, I'd be curious to know if any one of the popular PHP frameworks (CI, Cake, Symfony, etc.) actually use Reflection.

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

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

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

"Typically, this functionality will only be used by developers of server classes for the framework."

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

回答by codeassembly

The overhead is small so there is no big performance penalty other stuff like db, template processing etc are performance problems, test your framework with a simple action to see how fast it is.

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

For example the code bellow (frontcontroller) which uses reflection does it jobs in a few miliseconds

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

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

回答by XedinUnknown

I wanted something newer, so take a look at this repo. From the summary:

我想要更新的东西,所以看看这个 repo。从摘要:

  • PHP 7 is almost twice as fast as PHP 5 in case of reflections - This does not directly indicate that reflections are faster on PHP7, the PHP7 core have just received a great optimization and all code will benefit from this.
  • Basic reflections are quite fast - Reading methods and doc comments for 1000 classes cost just a few milliseconds. Parsing/Autoloading the classfiles does take a lot more time than the actual reflection mechanics. On our testsystem it takes about 300ms to load 1000 class files into memory (require/include/autoload) - And than just 1-5ms to use reflection parsing (doc comments, getMethods, etc...) on the same amount of classes.
  • Conclusion: Reflections are fast and in normal use cases you can ignore that performance impact. However, it is always recommended to only parse what is necessary. And, caching reflections doesn't give you any noticeable benefit on performance.
  • 在反射的情况下,PHP 7 的速度几乎是 PHP 5 的两倍——这并不直接表明 PHP7 上的反射速度更快,PHP7 核心刚刚得到了很好的优化,所有代码都将从中受益。
  • 基本反射非常快——读取 1000 个类的方法和文档注释只需几毫秒。解析/自动加载类文件确实比实际的反射机制花费更多的时间。在我们的测试系统上,将 1000 个类文件加载到内存中大约需要 300 毫秒(需要/包含/自动加载) - 而在相同数量的类上使用反射解析(文档注释、getMethods 等)只需要 1-5 毫秒。
  • 结论:反射速度很快,在正常使用情况下,您可以忽略这种性能影响。但是,始终建议仅解析必要的内容。而且,缓存反射不会给您带来任何显着的性能优势。

Also, check out another benchmark.

另外,请查看另一个基准测试

Those results were obtained on a development OS X machine using PHP 5.5.5. [...]

  • Read a single property on one object: The closure is slightly faster.

  • Read a single property on many objects: Reflection is way faster.

  • Reading all the properties of an object: The closure is faster.

  • Writing a single property on one object: Reflection is slightly faster.

  • Writing a single property on many objects: Reflection is way faster.

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

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

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

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

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

  • 在多个对象上编写单个属性:反射速度更快。

回答by Lu4

In my case reflection it is only 230% slower than calling class method directly, which as fast as call_user_func function.

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

回答by grantwparks

Sometimes using something like call_user_func_array() can get you what you need. Don't know how the performance differs.

有时使用 call_user_func_array() 之类的东西可以满足您的需求。不知道性能有什么不同。

回答by WiR3D

based on the code that @Alix Axel provided

基于@Alix Axel 提供的代码

So for completeness I decided to wrap each option in a class and include caching of objects where applicable. here was the results and code The results on PHP 5.6 on an i7-4710HQ

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

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

Code:

代码:

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

回答by Dre

CodeIgniter defenitly uses Reflections. And i bet the others also do. Look into Controller class in the system/controller folder in ci installation.

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