PHP 5 Reflection API производительность - PullRequest
55 голосов
/ 17 ноября 2008

В настоящее время я рассматриваю возможность использования классов Reflection (в основном ReflectionClass и ReflectionMethod) в моей собственной веб-инфраструктуре MVC, поскольку мне нужно автоматически создавать экземпляры классов контроллеров и вызывать их методы без какой-либо требуемой конфигурации (подход «соглашение по конфигурации») .

Я обеспокоен производительностью, хотя думаю, что запросы к базе данных, скорее всего, будут более узкими местами, чем реальный код PHP.

Итак, мне интересно, есть ли у кого-нибудь хороший или плохой опыт с PHP 5 Reflection с точки зрения производительности.

Кроме того, мне было бы интересно узнать, действительно ли какая-либо из популярных платформ PHP (CI, Cake, Symfony и т. Д.) Действительно использует Reflection.

Ответы [ 10 ]

56 голосов
/ 01 февраля 2013

Я протестировал эти 3 варианта (другой тест не разделял циклы процессора и был 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 (массив ('directCall', 'variableCall', 'refleCall'), 1000000));

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

И относительное время, также с 1 000 000 итераций (отдельный прогон):

ph () -> Dump (Benchmark (array ('directCall', 'variableCall', 'lectedCall '), 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;
}
54 голосов
/ 17 ноября 2008

Не беспокойся. Установите Xdebug и убедитесь, где находится узкое место.

Использование рефлексии обходится дорого, но имеет ли это значение, зависит от того, что вы делаете. Если вы реализуете контроллер / диспетчер запросов, используя Reflection, то это только одно использование на запрос. Абсолютно незначительный.

Если вы реализуете свой слой ORM с использованием отражения, используете его для каждого объекта или даже для каждого доступа к свойству и создаете сотни или тысячи объектов, тогда это может быть дорогостоящим.

18 голосов
/ 30 мая 2009

Вызов статической функции 1 миллион раз будет стоить ~ 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;

Очевидно, что фактическое влияние зависит от количества вызовов, которые вы ожидаете сделать

5 голосов
/ 16 февраля 2009

Кроме того, мне было бы интересно узнать, есть ли одна из популярных платформ PHP (CI, Торт, Symfony и т. Д.) На самом деле используют Отражение.

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

"Обычно эта функциональность будет использоваться только разработчиками серверных классов для платформы."

4 голосов
/ 03 января 2009

Издержки невелики, поэтому нет большой потери производительности, другие вещи, такие как 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);
    }
}
3 голосов
/ 26 февраля 2018

Я хотел что-то более новое, поэтому взгляните на это репо . Из резюме:

  • В случае отражений PHP 7 почти в два раза быстрее, чем PHP 5 - это не означает, что отражения в PHP7 быстрее Ядро PHP7 только что получило большую оптимизацию, и весь код будет выиграть от этого.
  • Основные размышления довольно быстрые - методы чтения и комментарии к документам для 1000 классов стоят всего несколько миллисекунд. Разбор / автозагрузка файлы классов занимают намного больше времени, чем фактическое отражение механика. На нашей тестовой системе для загрузки 1000 классов требуется около 300 мс файлы в память (требуют / включают / автозагрузка) - и только 1-5 мс использовать анализ отражения (комментарии к документу, getMethods и т. д.) для одного и того же количество уроков.
  • Заключение: отражения быстрые, и в обычных случаях использования вы можете игнорировать это влияние на производительность. Тем не менее, всегда рекомендуется разбирай только то что надо. И, кеширование отражений не дает Вам любая заметная выгода от производительности.

Кроме того, проверьте еще один тест .

Эти результаты были получены на компьютере под управлением OS X с использованием PHP 5.5.5. [...]

  • Чтение одного свойства для одного объекта: закрытие происходит немного быстрее.

  • Чтение одного свойства для многих объектов: отражение намного быстрее.

  • Чтение всех свойств объекта: закрытие происходит быстрее.

  • Запись одного свойства для одного объекта: Отражение немного быстрее.

  • Запись одного свойства для многих объектов: отражение намного быстрее.

2 голосов
/ 25 марта 2011

Иногда использование чего-то вроде call_user_func_array () может дать вам то, что вам нужно. Не знаю, чем отличается производительность.

2 голосов
/ 03 января 2011

В моем случае это только на 230% медленнее, чем прямой вызов метода класса, что так же быстро, как функция call_user_func.

1 голос
/ 24 апреля 2015

на основе кода, предоставленного @Alix Axel

Поэтому для полноты я решил обернуть каждую опцию в классе и включить кэширование объектов, где это применимо. здесь были результаты и код Результаты на PHP 5.6 на i7-4710HQ

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));
1 голос
/ 14 мая 2010

CodeIgniter определенно использует Reflections. И я держу пари, что и другие тоже. Посмотрите на класс Controller в папке system / controller при установке ci.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...