Производительность PHP 5 Reflection API
в настоящее время я рассматриваю использование классов отражения (в основном ReflectionClass и ReflectionMethod) в моей собственной веб-платформе MVC, потому что мне нужно автоматически создавать экземпляры классов контроллеров и вызывать их методы без какой-либо требуемой конфигурации (подход"соглашение по конфигурации").
меня беспокоит производительность, хотя я думаю, что запросы к базе данных, вероятно, будут большими узкими местами, чем фактический PHP-код.
Итак, я интересно, есть ли у кого-нибудь хороший или плохой опыт работы с PHP 5 Reflection с точки зрения производительности.
кроме того, мне было бы любопытно узнать, есть ли какой-либо из популярных фреймворков PHP (CI, Cake, Symfony и т. д.) на самом деле использовать отражение.
10 ответов:
Не беспокойтесь. Установить Xdebug и убедитесь, где узкое место.
есть стоимость использования отражения, но имеет ли это значение зависит от того, что вы делаете. Если вы реализуете диспетчер контроллера / запроса с помощью отражения, то это всего лишь одно использование на запрос. Абсолютно ничтожно.
Если вы реализуете свой слой ORM с помощью отражения, используйте его для каждого объекта или даже для каждого доступа к свойству и создавайте сотни или тысячи объектов, тогда это может быть дорого.
я сопоставил эти 3 варианта (другой тест не разделял циклы процессора и был 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 (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%).
здесь
вызов статической функции 1 миллион раз будет стоить ~ 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;
очевидно, что фактическое влияние зависит от количества вызовов, которые вы ожидаете сделать
кроме того, мне было бы любопытно узнать, есть ли один из популярных фреймворков PHP (CI, Торт, Symfony и др.) фактически использовать Отображение.
http://framework.zend.com/manual/en/zend.server.reflection.html
"как правило, эта функциональность будет использоваться только разработчиками классов сервер для рамок."
накладные расходы невелики, поэтому нет большого штрафа за производительность другие вещи, такие как БД, обработка шаблонов и т. д.-Это проблемы с производительностью, проверьте свою структуру с помощью простого действия, чтобы увидеть, насколько это быстро.
например, код ниже (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); } }
в моем случае отражение это только на 230% медленнее, чем вызов метода класса напрямую, что так же быстро, как функция call_user_func.
иногда с помощью чего-то вроде call_user_func_array() можно получить то, что вам нужно. Не знаю, как отличается производительность.
CodeIgniter defenitly использует отражения. И я держу пари, что другие тоже делают. Посмотрите в класс контроллера в папке system / controller в установке ci.
на основе кода, который @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));
Я хотел что-то новее, так что взгляните на этот РЕПО. Из резюме:
- PHP 7 почти в два раза быстрее PHP 5 в случае отражений - это напрямую не указывает на то, что отражения быстрее на PHP7, Ядро PHP7 только что получило отличную оптимизацию и весь код будет выиграть от этого.
- основные отражения-это довольно быстрые методы чтения, а комментарии doc для 1000 классов стоят всего несколько миллисекунд. Разбор/автоматическая загрузка classfiles занимает намного больше времени, чем реальное отражение механика. На нашей тестовой системе требуется около 300 мс для загрузки класса 1000 файлы в память (require/include / autoload) - и чем как раз 1-5ms к используйте разбор отражения (комментарии doc, getMethods и т. д...) на то же самое количество классов.
- вывод: отражения быстро и в обычных случаях использования вы можете игнорировать это влияние на производительность. Тем не менее, всегда рекомендуется только разбирать то, что есть необходимый. И, кэширование отражений не дает у вас какая-то заметная выгода по производительности.
кроме того, проверьте еще один тест.
эти результаты были получены на машине разработки OS X с использованием PHP 5.5.5. [...]
прочитайте одно свойство на одном объекте: закрытие немного быстрее.
читать одно свойство на многих объектах: отражение есть гораздо быстрее.
чтение всех свойств объекта: закрытие происходит быстрее.
запись одного свойства на одном объекте: отражение немного быстрее.
запись одного свойства на многих объектах: отражение намного быстрее.