samedi 16 novembre 2013

assert or not assert ?

Dernièrement, en voyant passer un lien vers une RFC sur la génération d'exception suite à une erreur d'assertion (PHP RFC: Expectations), il m'est revenu à l'esprit les raisons pour lesquelles je n'utilisais plus "assert" : les mauvaises performances en production.

Les assertions sont pourtant un outil très intéressant pour la conception d'application :  la programmation par contrat. L'idée intéressante aussi derrière les assertions PHP, c'est la possibilité de les désactiver en environnement de production, ce qui permet, théoriquement, de gagner en performance, surtout quand l'assertion est passée sous format "chaîne de caractères".

Par exemple :
assert('$o instanceof MaClasse');

Si l'assertion avait été écrite comme cela :
assert($o instanceof MaClasse);

Activé ou non, le contrôle du type de l'objet est quand même effectué.

En analysant le code du ZendFramework 2, j'ai constaté qu'il y avait beaucoup de contrôles de type. Par exemple, dans "\Zend\Stdlib\Hydrator\Filter\FilterComposite", dans le constructeur, on valide le filtre avec ceci : 
if (
    !is_callable($value)
    && !$value instanceof FilterInterface
   ) {
        throw new InvalidArgumentException(
            'The value of ' . $key . ' should be either a callable or ' .
             'an instance of Zend\Stdlib\Hydrator\Filter\FilterInterface'
         );
     }
Du coup, j'avais proposé que l'on utilise une assertion, mais on m'a dit que les performances n'étaient pas bonnes.
J'ai donc fait un bench, et en effet, les performances ne sont vraiment pas bonnes :
$o = new stdClass;

$start = microtime(true);
for($i = 0; $i <= 100000; $i++)
{
	if (!$o instanceof stdClass) {
		throw new RuntimeException("L'objet doit être une instance de la classe stdClass");
	}
}
echo 'Sans assertion : ', round((microtime(true) - $start) * 1000), "ms
";
assert_options(ASSERT_ACTIVE, 1);

$start = microtime(true);
for($i = 0; $i <= 100000; $i++)
{
	assert('$o instanceof stdClass', "L'objet doit être une instance de la classe stdClass");
}
echo 'Avec assertion active : ', round((microtime(true) - $start) * 1000), "ms
";

assert_options(ASSERT_ACTIVE, 0);

$start = microtime(true);
for($i = 0; $i <= 100000; $i++)
{
	assert('$o instanceof stdClass', "L'objet doit être une instance de la classe stdClass");
}
echo 'Sans assertion inactive : ', round((microtime(true) - $start) * 1000), "
";

Le résultat est sans appel :
Sans assertion : 50ms
Avec assertion active : 21426ms
Sans assertion inactive : 630ms


On constate qu'avec les assertions activées, les performances sont vraiment très mauvaises, mais ce n'est pas choquant (appel de fonction + interprétation à la volée), et en plus, c'est uniquement en environnement de développement et d'intégration.

Par contre, on constate qu'avec les assertions désactivées, on décuple le temps de contrôle par rapport à un simple "if".

Donc, assertion mon amour, il nous faut nous séparer !

Aucun commentaire:

Enregistrer un commentaire