Производительность API отражения PHP 5
В настоящее время я рассматриваю возможность использования классов отражения (в основном, ReflectionClass и ReflectionMethod) в моей собственной веб-платформе MVC, потому что мне нужно автоматически создавать экземпляры классов контроллеров и вызывать их методы без какой-либо требуемой конфигурации (подход "соглашение по конфигурации").
Меня беспокоит производительность, хотя я думаю, что запросы к базе данных, скорее всего, будут более узкими местами, чем фактический PHP-код.
Итак, мне интересно, если у кого-нибудь есть хороший или плохой опыт работы с PHP 5 с точки зрения производительности.
Кроме того, мне было бы любопытно узнать, действительно ли какой-либо из популярных фреймворков PHP (CI, Cake, Symfony и т.д.) использует отражение.
10 answers
Не беспокойтесь. Установите Xdebug и убедитесь, где находится узкое место.
Использование отражения сопряжено с определенными затратами, но имеет ли это значение, зависит от того, что вы делаете. Если вы реализуете диспетчер контроллеров/запросов с использованием отражения, то это всего лишь одно использование для каждого запроса. Абсолютно незначительный.
Если вы реализуете свой слой ORM с использованием отражения, используете его для каждого объекта или даже для каждого доступа к свойству и создаете сотни или тысячи объектов, то это может быть дорогостоящим.
Я провел сравнительный анализ этих 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(Эталонный(массив('Прямой вызов', 'Переменный вызов', 'Отраженный вызов'), 1000000));
Array
(
[directCall] => 4.13348770
[variableCall] => 6.82747173
[reflectedCall] => 8.67534351
)
И относительное время, также с 1 000 000 итераций (отдельный запуск):
Ph()->Дамп(Эталонный(массив ("Прямой вызов", "Переменный вызов", "Отраженный вызов"), 1000000, верно));
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 миллион раз на моей машине будет стоить ~ 0,31 секунды. При использовании метода отражения это стоит ~ 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, Cake, 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 защищенно использует отражения. И я держу пари, что другие тоже так делают. Посмотрите класс контроллера в папке system/controller в установке ci.
На основе кода, предоставленного @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));
Я хотел что-то новее, так что взгляните на это репо. Из резюме:
- PHP 7 почти в два раза быстрее PHP 5 в случае отражений - Это напрямую не указывает на то, что отражения быстрее на PHP7, ядро PHP7 только что получило отличную оптимизацию, и весь код выиграет от этого.
- Основные размышления - это довольно быстрые методы чтения, и комментарии к документам для 1000 классов стоят всего несколько миллисекунд. Синтаксический анализ/Автоматическая загрузка файлы классов действительно занимают намного больше времени, чем реальная механика отражения. В нашей тестовой системе требуется около 300 мс для загрузки 1000 файлов классов в память (требуется/включить/автоматическая загрузка) - И всего 1-5 мс для использования анализа отражения (комментарии к документам, GetMethods и т. Д.) На том же количество занятий.
- Вывод: Отражения выполняются быстро, и в обычных случаях использования вы можете игнорировать это влияние на производительность. Однако всегда рекомендуется анализировать только то, что необходимо. И, кэширование размышления не дают вам никакого заметного преимущества в производительности.
Кроме того, проверьте еще один тест.
Эти результаты были получены на машине разработки OS X с использованием PHP 5.5.5. [...]
Прочитайте одно свойство для одного объекта: закрытие происходит немного быстрее.
Считывание одного свойства для многих объектов: отражение происходит намного быстрее.
Считывание всех свойств объекта: Закрытие происходит быстрее.
Написание одного свойства для одного объекта: Отражение происходит немного быстрее.
Написание одного свойства для многих объектов: Отражение намного быстрее.