code

PHP 5 Reflection API 성능

codestyles 2020. 12. 27. 10:54
반응형

PHP 5 Reflection API 성능


필자는 현재 자체 MVC 웹 프레임 워크에서 Reflection 클래스 (주로 ReflectionClass 및 ReflectionMethod)를 사용하는 것을 고려하고 있습니다. 컨트롤러 클래스를 자동으로 인스턴스화하고 필요한 구성 ( "구성에 대한 규칙"접근 방식)없이 해당 메서드를 호출해야하기 때문입니다.

데이터베이스 요청이 실제 PHP 코드보다 더 큰 병목 현상이 발생할 가능성이 있다고 생각하지만 성능이 걱정됩니다.

따라서 성능 관점에서 PHP 5 Reflection에 대한 좋은 경험이나 나쁜 경험이 있는지 궁금합니다.

게다가 인기있는 PHP 프레임 워크 (CI, Cake, Symfony 등)가 실제로 Reflection을 사용하는지 알고 싶습니다.


걱정하지 마세요. Xdebug를 설치 하고 병목이 어디에 있는지 확인하십시오.

리플렉션을 사용하는 데는 비용이 들지만 그게 중요한지 여부는 수행중인 작업에 따라 다릅니다. Reflection을 사용하여 컨트롤러 / 요청 디스패처를 구현하면 요청 당 한 번만 사용됩니다. 전혀 무시할 수 있습니다.

리플렉션을 사용하여 ORM 계층을 구현하고 모든 개체 또는 속성에 대한 모든 액세스에 사용하고 수백 또는 수천 개의 개체를 만드는 경우 비용이 많이들 수 있습니다.


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

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 %로 감소 ).

누군가이 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;
}

정적 함수를 100 만 번 호출하면 내 컴퓨터에서 약 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;

분명히 실제 영향은 예상되는 통화 수에 따라 다릅니다.


게다가 인기있는 PHP 프레임 워크 (CI, Cake, Symfony 등)가 실제로 Reflection을 사용하는지 알고 싶습니다.

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

"일반적으로이 기능은 프레임 워크 용 서버 클래스 개발자 만 사용할 것입니다."


오버 헤드가 적기 때문에 큰 성능 저하가 없습니다. 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);
    }
}

나는 더 새로운 것을 원 했으므로이 저장소를 살펴보십시오 . 요약에서 :

  • PHP 7은 리플렉션의 경우 PHP 5보다 거의 두 배 빠릅니다. 이것은 리플렉션이 PHP7에서 더 빠르다는 것을 직접적으로 나타내지는 않습니다. PHP7 코어는 방금 훌륭한 최적화를 받았으며 모든 코드가 이로부터 혜택을받을 것입니다.
  • 기본 반영은 매우 빠릅니다. 1000 개의 클래스에 대한 읽기 방법과 문서 주석은 몇 밀리 초에 불과합니다. 클래스 파일의 구문 분석 / 자동로드는 실제 반사 메커니즘보다 훨씬 더 많은 시간이 걸립니다. 테스트 시스템에서 1000 개의 클래스 파일을 메모리에로드하는 데 약 300ms가 걸립니다 (필수 / 포함 / 자동로드)-동일한 양의 클래스에서 리플렉션 파싱 (문서 주석, getMethods 등)을 사용하는 데 1-5ms가 걸립니다.
  • 결론 : 반사는 빠르며 일반적인 사용 사례에서는 성능 영향을 무시할 수 있습니다. 그러나 항상 필요한 것만 구문 분석하는 것이 좋습니다. 그리고 캐싱 리플렉션은 성능에 눈에 띄는 이점을 제공하지 않습니다.

또한 다른 벤치 마크를 확인하십시오 .

이러한 결과는 PHP 5.5.5를 사용하는 개발 OS X 시스템에서 얻은 것입니다. [...]

  • 하나의 객체에 대한 단일 속성 읽기 : 클로저가 약간 더 빠릅니다.

  • 여러 개체에 대한 단일 속성 읽기 : 반사가 훨씬 빠릅니다.

  • 객체의 모든 속성 읽기 : 클로저가 더 빠릅니다.

  • 하나의 객체에 단일 속성 쓰기 : 반사가 약간 더 빠릅니다.

  • 여러 객체에 단일 속성 작성 : 반사가 훨씬 빠릅니다.


제 경우에는 call_user_func 함수만큼 빠른 클래스 메서드를 직접 호출하는 것보다 230 % 만 느립니다.


때때로 call_user_func_array ()와 같은 것을 사용하면 필요한 것을 얻을 수 있습니다. 성능이 어떻게 다른지 모릅니다.


CodeIgniter는 반사를 방어 적으로 사용합니다. 그리고 나는 다른 사람들도 할 것이라고 확신합니다. ci 설치의 system / controller 폴더에서 Controller 클래스를 확인하십시오.


@Alix Axel이 제공 한 코드를 기반으로

그래서 완성도를 위해 각 옵션을 클래스로 래핑하고 적용 가능한 경우 객체 캐싱을 포함하기로 결정했습니다. 여기에 결과와 코드가 있습니다. i7-4710HQ에서 PHP 5.6의 결과

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

참조 URL : https://stackoverflow.com/questions/294582/php-5-reflection-api-performance

반응형