web-dev-qa-db-fra.com

PHP 5 performances de l'API de réflexion

Je compte actuellement l'utilisation de classes de réflexion (classes de réflexion et réflexionMethod principalement) dans mon propre cadre Web MVC, car j'ai besoin d'instancher automatiquement les classes de contrôleur et d'appeler leurs méthodes sans aucune configuration requise (approche "Convention sur la configuration").

Je suis préoccupé par la performance, même si je pense que les demandes de base de données sont susceptibles d'être de plus gros goulots d'étranglement que le code réel PHP.

Donc, je me demande si quelqu'un a une bonne ou une bonne expérience avec PHP 5 réflexion d'un point de vue de la performance.

En outre, je serais curieux de savoir si l'un des populaires PHP Cadres (CI, gâteau, symfony, etc.) utilise réellement une réflexion.

56
Franck

Ne vous inquiétez pas. Installation xdebug et assurez-vous d'où le goulot d'étranglement est.

Il y a des coûts d'utilisation de la réflexion, mais si cette question dépend de ce que vous faites. Si vous implémentez un répartiteur de contrôleur/demande à l'aide de la réflexion, il s'agit simplement d'une seule utilisation par demande. Absolument négligeable.

Si vous implémentez votre couche Orm à l'aide de la réflexion, utilisez-la pour chaque objet ou même tous les accès à une propriété, créez des centaines ou des milliers d'objets, alors cela pourrait être coûteux.

54
Kornel

J'ai comparé ces 3 options (l'autre repère ne divise pas les cycles du processeur et était de 4 ans):

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

Le temps absolu pris pour 1 000 000 itérations:

print_r (Benchmark (tableau ("DirecteurCall", "Variablecall", "réflexionCall"), 1000000));

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

Et l'heure relative, également avec 1 000 000 itérations (course séparée):

pH () -> Dump (Benchmark (tableau ("DirectCall", "Variablecall", "réflexionCall"), 1000000, True));

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

Il semble que la performance de réflexion ait été considérablement augmentée en 5.4.7 (d'environ 500% jusqu'à ~ 213%).

Voici la fonction Benchmark() Fonction que j'ai utilisée si quelqu'un veut réexécuter cette référence:

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

En outre, je serais curieux de savoir si l'un des populaires PHP Cadres (CI, gâteau, symfony, etc.) utilise réellement une réflexion.

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

"Typiquement, cette fonctionnalité ne sera utilisée que par les développeurs de classes de serveurs pour le cadre."

5
vartec

Les frais généraux sont petits, de sorte qu'il n'y a pas de pénalité de grande performance, d'autres choses comme DB, le traitement des gabarits, etc. sont des problèmes de performance, testez votre cadre avec une simple action pour voir à quel point c'est rapide.

Par exemple, le code ci-dessous (FrontController) qui utilise une réflexion fait-il des emplois dans quelques milisecondes

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

Je voulais quelque chose de plus récent, alors jetez un coup d'œil à ce repo . Du résumé:

  • PHP 7 est presque deux fois plus rapide que PHP 5 en cas de réflexions - cela n'indique pas directement que les réflexions sont plus rapides sur PHP7, le noyau PHP7 vient de recevoir une excellente optimisation et tout code bénéficiera à partir de là.
  • Les réflexions de base sont des méthodes de lecture assez rapides et des commentaires DOC pour 1000 classes coûtent seulement quelques millisecondes. Analysage/AutoLoading Les camarades de classe prennent beaucoup plus de temps que les mécanismes de réflexion. Sur notre test de test, il faut environ 300 ms pour charger 1000 fichiers de classe en mémoire (nécessite/incluant/AutoLoad) - et seulement 1-5ms d'utiliser une analyse de réflexion (commentaires doc, getMethods, etc.) sur le même nombre de classes.
  • Conclusion: les réflexions sont rapides et dans des cas d'utilisation normale, vous pouvez ignorer cet impact sur la performance. Cependant, il est toujours recommandé de seulement analyser ce qui est nécessaire. Et, les reflets de mise en cache ne vous donnent aucun avantage remarquable sur la performance.

En outre, vérifiez ne autre référence .

Ces résultats ont été obtenus sur une machine de développement OS X en utilisant PHP 5.5.5. [...]

  • Lisez une propriété unique sur un objet: la fermeture est légèrement plus rapide.

  • Lisez une seule propriété sur de nombreux objets: la réflexion est de façon plus rapide.

  • Lire toutes les propriétés d'un objet: la fermeture est plus rapide.

  • Écrire une propriété unique sur un objet: la réflexion est légèrement plus rapide.

  • Écrire une propriété unique sur de nombreux objets: la réflexion est de façon plus rapide.

3
XedinUnknown

Dans ma réflexion de cas, il ne représente que 230% plus lentement que la méthode de la classe appelante directement, qui aussi vite que la fonction Call_user_func.

2
Lu4

Parfois, en utilisant quelque chose comme Call_user_func_array () peut vous obtenir ce dont vous avez besoin. Je ne sais pas comment la performance diffère.

2
grantwparks

LediconInter utilise déficiblement des réflexions. Et je parie que les autres font aussi. Recherchez dans la classe Controller dans le dossier System/Controller de l'installation CI.

1
Dre