web-dev-qa-db-ja.com

PHP 5リフレクションAPIのパフォーマンス

現在、自分のMVC WebフレームワークでReflectionクラス(ReflectionClassとReflectionMethod)の使用を検討しています。これは、コントローラークラスを自動的にインスタンス化し、必要な構成なしにメソッドを呼び出す必要があるためです(「Convention over Configuration」アプローチ)。

データベースのリクエストは実際のPHP=コードよりも大きなボトルネックになる可能性が高いと思いますが、パフォーマンスについて心配しています。

ですから、PHP 5パフォーマンスの観点からの考察)で良い経験も悪い経験もあるかどうか疑問に思います。

さらに、人気のあるPHPフレームワーク(CI、Cake、Symfonyなど))のいずれかが実際にReflectionを使用しているかどうかを知りたいと思います。

56
Franck

心配しないでください。 Xdebug をインストールし、ボトルネックの場所を確認します。

リフレクションの使用にはコストがかかりますが、それが重要かどうかは、あなたが何をしているかに依存します。リフレクションを使用してコントローラー/リクエストディスパッチャーを実装する場合、リクエストごとに1回だけ使用できます。絶対に無視できます。

リフレクションを使用してORMレイヤーを実装し、それをすべてのオブジェクトまたはプロパティへのすべてのアクセスに使用し、数百または数千のオブジェクトを作成すると、コストがかかる可能性があります。

54
Kornel

私はこれら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;
}
_
56
Alix Axel

さらに、人気のあるPHPフレームワーク(CI、Cake、Symfonyなど))のいずれかが実際にReflectionを使用しているかどうかを知りたいと思います。

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

"通常、この機能はフレームワークのサーバークラスの開発者のみが使用します。"

5
vartec

オーバーヘッドは小さいので、db、テンプレート処理などのパフォーマンス上の問題は他に大きなパフォーマンスペナルティはありません。単純なアクションでフレームワークをテストし、その速度を確認します。

たとえば、リフレクションを使用する以下のコード(フロントコントローラー)は、数ミリ秒で機能します

<?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);
    }
}
4
codeassembly

もっと新しいものが欲しかったので、 this repo を見てください。要約から:

  • PHP 7は、PHP 5の約2倍の速さです。リフレクションの場合-これは、リフレクションがPHP7で高速であることを直接示すものではありません。PHP7コアが優れた最適化を受けただけで、すべてのコードにメリットがあります。これから。
  • 基本的なリフレクションは非常に高速です-1000クラスの読み取りメソッドとドキュメントコメントは数ミリ秒しかかかりません。クラスファイルの解析/オートロードは、実際の反射メカニズムよりも多くの時間がかかります。私たちのテストシステムでは、1000個のクラスファイルをメモリにロードするのに約300ミリ秒(require/include/autoload)が必要です。同じ量のクラスでリフレクション解析(docコメント、getMethodsなど)を使用するには、わずか1〜5ミリ秒かかります。
  • 結論:リフレクションは高速であり、通常の使用例では、パフォーマンスへの影響を無視できます。ただし、常に必要なものだけを解析することをお勧めします。また、リフレクションをキャッシュしても、パフォーマンスに顕著なメリットはありません。

また、 別のベンチマーク も確認してください。

これらの結果は、PHP 5.5.5。[...]を使用して開発OS Xマシンで取得されました。

  • 1つのオブジェクトの1つのプロパティを読み取ります。クロージャの方が少し高速です。

  • 多くのオブジェクトの単一のプロパティを読み取ります。反射はは​​るかに高速です。

  • オブジェクトのすべてのプロパティを読み取る:クロージャはより高速です。

  • 1つのオブジェクトに単一のプロパティを書き込む:リフレクションはわずかに高速です。

  • 多くのオブジェクトに単一のプロパティを書き込む:リフレクションの方がはるかに高速です。

3
XedinUnknown

私の場合、リフレクションはクラスメソッドを直接呼び出すよりも230%遅く、call_user_func関数と同じくらい高速です。

2
Lu4

Call_user_func_array()などを使用すると、必要なものが得られる場合があります。パフォーマンスの違いがわかりません。

2
grantwparks

CodeIgniterは確実にReflectionsを使用します。そして、私は他の人もそうするに違いない。 CIインストールのsystem/controllerフォルダーでControllerクラスを調べます。

1
Dre

@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
WiR3D