Benchmarking PHP – Partie 2

Voici la suite du premier article de cette série sur les performances de PHP.

Je me suis intéressé cette fois-ci à un autre « côté pervers » des langages de scripting tels que PHP, à savoir leur aspect faiblement typé.

En programmation, deux tendances s’opposent : le typage fort (ou typage strict) et le typage faible. Comme son nom le laisse entendre, le typage se rapporte aux types de données utilisés couramment : nombre entiers, nombres réels, chaînes de caractères, etc…

Dans un langage fortement typé, les fonctions ne peuvent être appelées qu’avec les types de données pour lesquels elles ont été conçues. Si une fonction requiert que son premier paramètre soit en entier, vous obtiendrez une erreur de compilation si vous essayez de lui envoyer une chaîne de caractères.

La plupart des langage de scripting sont, quant à eux, faiblement typés, c’est-à-dire, qu’il est possible d’appeler des fonctions avec un autre type de données que celui avec lequel elles ont été conçues. Dans certains cas, comme JavaScript ou PHP avant sa version 5, les paramètres de fonction n’ont même pas de type, uniquement un nom. Cela simplifie la programmation mais complique le débogage dans certains cas, notamment avec les objets. Avec l’arrivée de PHP 5, il est possible d’appliquer un typage fort sur certains paramètres de fonction. La raison principale de ce comportement est que les langages de scripting sont compilés au moment de leur exécution.

Etat des lieux

Dans le cas de PHP, c’est à sa capacité à traiter des chaînes de caractères comme des nombres que j’ai choisi de m’intéresser.

Le code suivant est parfaitement légal en PHP :

$a = 3;
$b = '4';
$c = $a + $b;

Le résultat sera bien 7.

Mais travailler avec des chaînes de caractères comme avec des entiers n’est-il pas plus long que de travailler directement avec des entiers ?
La réponse théorique est évidente, mais dans la pratique, certains éléments font que les choses ne sont pas si simples.

Le protocole de test

Pour ce test, j’ai choisi d’effectuer 1000 fois l’addition de tableaux d’entiers contenant :

  1. 100 000 entiers aléatoires natifs
  2. 100 000 entiers aléatoires sous forme de chaînes de caractères
  3. 100 000 entiers aléatoires sous forme de chaînes de caractères qui seront convertis « manuellement » au moment du traitement

Tous les entiers aléatoires sont compris entre 0 et 100 inclus.

Résultats

Les résultats sont évidents :

Somme de 100 000 entiers natifs (1000 itérations) : 2.017847 s
Somme de 100 000 entiers sous forme de strings (1000 itérations) : 9.314373 s
Somme de 100 000 entiers sous forme de strings convertis en natifs (1000 itérations) : 57.042635 s

Les entiers natifs sont les grands vainqueurs, avec un calcul plus de 4x plus rapide que les entiers sous forme de chaîne de caractères.

J’ai poussé les tests avec des entiers compris entre 0 et 1 000 000. Les résultats montrent que la longueur des chaînes de caractères (plus le nombre est grand, plus la chaîne est longue) influent sur les performances globales. Dans le cas présent, les entiers natifs sont presque 6x plus rapide que les chaînes de caractères.

Somme de 100 000 entiers natifs (1000 itérations) : 2.561392 s
Somme de 100 000 entiers sous forme de strings (1000 itérations) : 12.046772 s
Somme de 100 000 entiers sous forme de strings convertis en natifs (1000 itérations) : 55.341405 s

PHP incite-t-il à l’usage des chaînes de caractères ?

A vrai dire, cela n’est pas le cas. Le typage faible de PHP permet cette utilisation aberrante des chaînes de caractères, mais rien dans son fonctionnement ne force à aller dans ce sens. En revanche, l’utilisation de PHP avec MySQL incite fortement à ce comportement.

Les extensions de PHP qui permettent de communiquer avec MySQL présentent une étrangeté. Toutes les valeurs retournées par MySQL lors d’une requête (exceptée NULL) sont des chaînes de caractères, que le champ original contienne un entier ou bien tout à fait autre chose. Du coup, ce que montre ce test, c’est que nous gagnerions beaucoup à ce que MySQL permette de renvoyer à PHP les types natifs de ses champs, mais aujourd’hui aucune possibilité à ma connaissance n’est proposée dans ce sens (que ce soit du côté de MySQL ou de PHP).

Mais alors pourquoi vouloir utiliser des entiers natifs ?

Il y a quelques cas que j’ai rencontrés personnellement, récemment, où l’utilisation d’entiers natifs a son avantage. Le plus courant est l’encodage JSON, qui permet de faire transiter de manière simplifiée des données.

Considérons les deux alternatives que sont un tableau d’entiers sous forme de chaînes de caractères (A) et le même tableau sous forme d’entiers (B), voilà ce qui ressort en JSON :

A = ["1","2","3","4","5"]
B = [1,2,3,4,5]

Vous pourrez constater deux choses :

  1. les nombres sont entourés de guillemets (« ) pour indiquer à JSON qu’il s’agit de chaînes de caractères et donc le résultat encodé en JSON est plus long
  2. lorsque ces informations encodées en JSON vont être décodées de l’autre côté, ce sont des chaînes de caractères qui vont être générées. Si le destinataire est un langage faiblement typé, tout va bien, mais s’il s’agit d’un langage fortement typé comme Java, attention aux dégâts.

Astuce : convertir des chaînes en entiers

Voilà un petit code source très sommaire mais qui résume à lui seul la méthode à employer pour convertir un tableau d’entiers sous forme de chaînes de caractères en tableau d’entiers natifs :

$tableau = array('1', '2', '3');
$tableau_natifs = array_map('intval', $tableau);

Code du test

<?php
    header('Content-type: text/plain; charset=UTF-8');
    set_time_limit(0);

    // Construction du tableau d'entiers natifs
    $intArray = array();
    for ($i = 0; $i < 100000; $i++)
        $intArray[] = mt_rand(0, 1000000);

    // Construction du tableau d'entiers sous forme de strings
    $strArray = array_map('strval', $intArray);

    // Temps de calcul avec le tableau d'entiers natifs
    $debut = microtime(true);
    for ($i = 0; $i < 1000; $i++)
        array_sum($intArray);
    $elapsed = microtime(true) - $debut;
    printf("Somme de 100 000 floats natifs (1000 itérations) : %f s\n\n", $elapsed);

    // Temps de calcul avec le tableau d'entiers sous forme de strings
    $debut = microtime(true);
    for ($i = 0; $i < 1000; $i++)
        array_sum($strArray);
    $elapsed = microtime(true) - $debut;
    printf("Somme de 100 000 floats sous forme de strings (1000 itérations) : %f s\n\n", $elapsed);

    // Temps de calcul avec le tableau d'entiers sous forme de strings avec conversion vers entiers natifs
    $debut = microtime(true);
    for ($i = 0; $i < 1000; $i++)
        array_sum(array_map('intval', $strArray));
    $elapsed = microtime(true) - $debut;
    printf("Somme de 100 000 floats sous forme de strings convertis en natifs (1000 itérations) : %f s\n\n", $elapsed);
?>
Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s