PHP 5 Reflection API Performance

I am currently considering using the Reflection classes (ReflectionClass and ReflectionMethod) in my own MVC web environment, because I need to automatically launch the controller classes and call their methods without any necessary configuration (the configuration convention approach).

I'm worried about performance, although I think database queries are likely to be bigger bottlenecks than actual PHP code.

So I'm wondering if anyone has any good or bad experience with PHP 5 Reflection in terms of performance.

Also, I would be interested to know if any of the popular PHP frameworks (CI, Cake, Symfony, etc.) use Reflection.

+52
performance reflection php
Nov 16 '08 at 23:41
source share
9 answers

Do not worry. Install Xdebug and see where the bottleneck is.

There is a cost to using reflection, but whether it depends on what you do. If you use the dispatcher / request manager using Reflection, then it is used only for each request. Absolutely insignificant.

If you implement your ORM layer using reflection, use it for every object or even for every access to the property and create hundreds or thousands of objects, then this can be expensive.

+46
Nov 16 '08 at 23:43
source share

I compared these 3 options (another test did not break CPU cycles and was 4y old):

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

Absolute time spent on 1,000,000 iterations:

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

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

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

ph () β†’ Dump (Benchmark (array ('directCall', 'variableCall', 'reflectCall'), 1000000, true));

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

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

Here is the Benchmark() function that I used if someone wants to repeat this test:

 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; } 
+52
Feb 01 '13 at 7:45
source share

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

This is the code I used by:

 <?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 make.

+18
May 30 '09 at 8:48 a.m.
source share

Also, I would be interested to know if one of the popular PHP frameworks (CI, Cake, Symphony, etc.) actually uses Reflection.

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

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

+5
Feb 16 '09 at 18:10
source share

The overhead is small, so there is no big penalty for productivity. Other things like db, template processing, etc. - performance problems, test your framework with a simple action to find out how fast it is.

For example, the following code (frontcontroller), which uses reflection, performs tasks in a few milliseconds

 <?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 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 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); } } 
+4
Jan 03 '09 at 16:22
source share

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

+2
Jan 03 2018-11-11T00:
source share

Sometimes using something like call_user_func_array () can give you what you need. I don’t know how performance is different.

+2
Mar 25 '11 at 6:08
source share

CodeIgniter defenitly uses Reflections. And I'm sure others do too. Examine the Controller class in the system / controller folder in the ci installation.

+1
May 14 '10 at 8:15
source share

based on the code that @Alix Axel provided

So, for completeness, I decided to wrap each option in a class and enable object caching where applicable. here were the results and code Results in PHP 5.6 on i7-4710HQ

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

The 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)); 
+1
Apr 24 '15 at 12:32
source share



All Articles