Benchmarking PHP – Partie 3

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

PHP est un langage particulièrement permissif et tolérant, qui continue notamment de fonctionner correctement même si une variable n’a pas été définie (à l’exception des propriétés d’objet).
En effet, PHP n’émet qu’un simple avertissement (de type E_NOTICE) dans ces cas-là et continue son exécution normalement.

Le code suivant :

$b = 1 + $a;

produira simplement le message :

Notice: Undefined variable: a in test.php on line 2

La grande tolérance à ce genre d’erreurs de PHP et de mauvaises habitudes de programmation ont entraîné la banalisation de ces messages et il est généralement admis de ne pas en tenir compte. PHP propose en effet de filtrer les messages d’erreur par type. Ainsi, la commande suivante est préconisée par de nombreuses distributions de PHP :

error_reporting(E_ALL ^ E_NOTICE);

Comme son nom l’indique vaguement, cette commande indique à PHP de rapporter toutes les erreurs, à l’exception du type E_NOTICE.

Mais le fait de ne pas rapporter ces erreurs n’a-t-il aucune autre conséquence sur l’exécution de PHP elle-même ?

Etat des lieux

Lorsqu’une erreur est déclenchée dans le code PHP, quel que soit son niveau (et donc y compris pour les erreurs de type E_NOTICE), un appel à la fonction native trigger_error() est exécuté.
Il est de notoriété publique que l’exécution de cette fonction soit lente.

Ne pas rapporter les erreurs concernées entraîne-t-il un gain de temps au niveau de l’exécution ?

Le protocole de test

Pour ce test, j’ai choisi d’exécuter les cas suivants :

  1. ajouter 10 millions de fois la valeur d’une variable non définie,
  2. exécuter la même addition sans rapporter les erreurs au journal global des erreurs de PHP,
  3. exécuter la même addition mais en testant à chaque itération l’existence de la variable,
  4. exécuter l’addition avec une variable définie

Tous les cas ont été exécutés avec la commande :

error_reporting(E_ALL ^ E_NOTICE);

Résultats

Les résultats sont édifiants :

Variable non définie + log des erreurs : 7.55 s
Variable non définie + pas de log des erreurs : 7.29 s
Variable non définie mais testée : 1.08 s
Variable définie : 1.09 s

La conclusion est simple. Les déclenchements de la fonction trigger_error() dans les cas de variables non définies prennent beaucoup de temps. Le code source s’exécute ici 7 fois plus lentement qu’avec une variable dument définie.

PHP incite-t-il au laxisme ?

Mon avis sur la question est tranché : Oui.

En effet, l’autre inconvénient de ne pas rapporter les erreurs de type E_NOTICE est de laisser passer des erreurs parfois dommageables.

L’exemple ci-dessous ne crée aucune erreur « visible » et pourtant n’apporte pas la solution attendue :

error_reporting(E_ALL ^ E_NOTICE);

function valide_si_conforme($txt) {
    return (empty($texte) || preg_match('/^[a-z]+$/', $txt));
}
echo valide_si_conforme('123456789');

En effet, la fonction valide_si_conforme() devrait renvoyer un résultat positif uniquement si le paramètre $txt est vide ou ne contient que des lettres minuscules.

L’appel :

valide_si_conforme('123456789')

devrait renvoyer 0 ou false.

Il n’en est rien et le script renvoie 1 ou true. L’erreur qui inscrit le paramètre $texte en lieu et place de $txt dans l’appel à empty() déclenche une erreur fonctionnelle mais PHP n’en tient pas compte et la détection de ce problème est alors laissée à l’appréciation du développeur. Et si celui-ci n’a pas la discipline ou l’expérience suffisante pour savoir où il faut et où il ne faut pas tester une valeur, de nombreuses erreurs sont susceptibles d’apparaître.

Mais alors que faire ?

La logique voudrait que toutes les erreurs soient rapportées et que les variables soient testées, tant sur leur valeur que sur leur existence.
Cette démarche m’a personnellement évité beaucoup de problèmes par le passé ou permis de corriger bon nombre de bugs dans des codes sources tiers.

Code du test

<?php
    error_reporting(E_ALL ^ E_NOTICE);
    header("Content-Type: text/plain; charset=UTF-8");

    printf("Variable non définie + log des erreurs : ");
    $start = microtime(true);
    $b = 0;
    for ($i = 0; $i < 10000000; $i++)
        $b += $a;
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);

    printf("Variable non définie + pas de log des erreurs : ");
    ini_set('log_errors', 0);
    $start = microtime(true);
    $b = 0;
    for ($i = 0; $i < 10000000; $i++)
        $b += $a;
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);

    printf("Variable non définie mais testée : ");
    $start = microtime(true);
    $b = 0;
    for ($i = 0; $i < 10000000; $i++) {
        if (isset($a))
            $b += $a;
    }
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);

    printf("Variable définie : ");
    $start = microtime(true);
    $b = 0;
    $a = 1;
    for ($i = 0; $i < 10000000; $i++)
        $b += $a;
    $elapsed = microtime(true) - $start;
    printf("%0.2f s\n", $elapsed);
?>
Publicités

Benchmarking Java – Tableaux contre Vecteurs

Java fait partie de ces langages de programmation qui offrent beaucoup d’outils d’aide au développement sensés faciliter et améliorer nos productions. Cependant, certains d’entre eux sont parfois utilisés de manière contre-productive.

Les Vecteurs

Un « vecteur » désigne un ensemble d’éléments stockés de manière contigüe les uns à la suite des autres. On appelle également les vecteurs des « tableaux ». Les tableaux sont des types primitifs des langages de programmation et n’offrent souvent en eux-mêmes que peu d’outils aidant à leur gestion. Il faut en général passer par des fonctions spécifiques.

Java intègre une autre approche en proposant des classes utilitaires (dans le package java.util) dont la classe Vector qui reproduit l’illusion d’un vecteur en ajoutant tout en tas de fonctionnalités améliorées (réallocation dynamique de la taille du tableau, insertion en milieu de tableau, …).

Mais comme nous allons le voir, tout cela a un coût.

Le test

Pour évaluer les performances des Vector Java contre les tableaux primitifs du langage, je procède à l’addition des 100 000 premiers entiers (0 inclus) et ce 10 000 fois successives.

Les résultats sont sans appel :

Entiers directs - Compteur classique :
======================================
Somme obtenue : 4999950000
Durée totale : 339 ms
Durée moyenne d'une itération : 0,033900 ms

Entiers directs - Boucle Java :
======================================
Somme obtenue : 4999950000
Durée totale : 601 ms
Durée moyenne d'une itération : 0,060100 ms

Vecteur - Compteur classique :
======================================
Somme obtenue : 4999950000
Durée totale : 41245 ms
Durée moyenne d'une itération : 4,124500 ms

Vecteur - Itérateur :
======================================
Somme obtenue : 4999950000
Durée totale : 41500 ms
Durée moyenne d'une itération : 4,150000 ms

Vecteur - Boucle Java :
======================================
Somme obtenue : 4999950000
Durée totale : 41874 ms
Durée moyenne d'une itération : 4,187400 ms

Les Vector sont jusqu’à 122 fois plus lents que les tableaux primitifs.

Mais pourquoi ?

Les tableaux primitifs sont capables de stocker l’ensemble des types proposés par Java, des entiers primitifs aux Object. On accède donc directement à l’élément primitif sans conversion de type et sans appel de fonction superflu.

En revanche, les classes génériques comme Vector n’acceptent que des Object ou leurs dérivés (dans notre cas, des objets de la classe Integer). Notre addition provoque donc des allocations d’objets et des conversions vers les types primitifs du langage qui sont très coûteux en temps, et cela réduit donc considérablement les performances.

Mais cela n’est pas tout, la classe Vector effectue tout un tas de vérification lors de l’accès aux éléments et c’est cette étape qui est très gourmande en ressource de calcul. En effet, en reproduisant le test dans notre tableau en remplaçant des entiers primitifs par des objets Integer, les Vector sont toujours 30 fois plus lents.

Conclusion

  • Si votre programme nécessite des performances très élevées, privilégiez des tableaux classiques
  • Si votre programme nécessite des variations fréquentes dans la taille du tableau alloué, envisagez éventuellement des Vector si la première règle n’est pas contredite.

Code du test

import java.util.Iterator;
import java.util.Vector;

public class Benchmark {

    public static final int NB_INTS = 100000;
    public static final int NB_PASS = 10000;
    
    public static void main(String[] args) {
        int[] directInts = new int[NB_INTS];
        Vector<Integer> intVector = new Vector<Integer>(NB_INTS);
        
        // Remplissage
        for (int i = 0; i < NB_INTS; i++) {
            directInts[i] = i;
            intVector.add(i);
        }
        
        // Parcours du tableau en utilisant un compteur classique
        long start = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c = 0; c < directInts.length; c++)
                sum += directInts[c];
        }
        long elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Entiers directs - Compteur classique :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du tableau en utilisant la boucle Java
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c : directInts)
                sum += c;
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Entiers directs - Boucle Java :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du vecteur en utilisant un compteur classique
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c = 0; c < intVector.size(); c++)
                sum += intVector.elementAt(c);
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Vecteur - Compteur classique :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du vecteur en utilisant un itérateur
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            Iterator<Integer> it = intVector.iterator(); 
            while (it.hasNext())
                sum += it.next();
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Vecteur - Itérateur :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
        
        // Parcours du vecteur en utilisant une boucle Java
        start = System.currentTimeMillis();
        sum = 0;
        for (int i = 0; i < NB_PASS; i++) {
            sum = 0;
            for (int c : intVector)
                sum += c;
        }
        elapsed = System.currentTimeMillis() - start;
        
        System.out.println("Vecteur - Boucle Java :");
        System.out.println("======================================");
        System.out.format("Somme obtenue : %d\n", sum);
        System.out.format("Durée totale : %d ms\n", elapsed);
        System.out.format("Durée moyenne d'une itération : %f ms\n", (double) (elapsed / (double) NB_PASS));
        System.out.println("");
    }

}

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

Benchmarking PHP – Partie 1

Pour ceux qui ne connaîtrait pas encore PHP, je vous laisse découvrir sa définition sur le site officiel.

Comme tous les langages de scripting, PHP est un outil très permissif et configurable à volonté qui facilite grandement le travail des développeurs.
Cependant, la permissivité des outils est parfois source de mauvaises habitudes pour les développeurs et ce qui peut apparaître comme étant une fonctionnalité intéressante pour gagner du temps, s’avère en fait être un problème en terme de performance au final.

Le premier benchmark de cette série porte sur les différentes façons d’accéder à une entrée de tableau associatif sans vérifier son existence au préalable ou, au contraire, en vérifiant son éligibilité selon plusieurs méthodes.

Protocole de test

Pour tester les performances de chacun des cas suivants, nous tentons d’accéder une entrée inexistante ‘id’ du tableau associatif $_GET et vérifions que sa valeur est différente de 0. Pour faire apparaître d’éventuelles différences dans nos tests, ils seront effectués sur un million d’itérations.

Voici, les différents tests qui seront effectués dans ce benchmark :

1. Aucune protection

Dans le premier test, nous n’effectuons aucun test d’existence de l’entrée dans le tableau. Le code se résume donc à :

if ($_GET['id'] != 0)

2. Protection avec array_key_exists() :

Dans le deuxième test, nous vérifions que la clé ‘id’ existe au sein du tableau $_GET grâce à la fonction array_key_exists(). Le code se résume cette fois-ci par :

if (array_key_exists('id', $_GET) && $_GET['id'] != 0)

3. Protection avec isset() :

Dans le troisième test, nous vérifions simplement si $_GET[‘id’] a été défini avant de tester sa valeur grâce à la fonction isset(). Le code est cette fois-ci :

if (isset($_GET['id']) && $_GET['id'] != 0)

4. Test avec empty() :

Dans le dernier test, nous vérifions uniquement si la variable $_GET[‘id’] est vide ou non grâce à la fonction empty(). Le code du test est donc :

if (!empty($_GET['id']))

Résultats

Voilà les résultats de l’exécution des tests :

Nombre d'itérations : 1000000

Aucune protection : 0.652613162994 ms
Protection avec array_key_exists() : 0.448069095612 ms
Protection avec isset() : 0.246340990067 ms
Protection avec empty() : 0.248647928238 ms

Nos grands gagnants sont donc isset() et empty().

Comme pouvait le laisser supposer l’introduction de cet article, ne pas protéger ses tests de variable est permis par PHP mais entraîne une réduction de performances. Le surcoût de vouloir accéder à une variable qui n’existe pas est que PHP déclenche une erreur de type E_NOTICE. Le réglage par défaut de PHP induit que cette erreur ne sera pas reportée à l’écran pour l’utilisateur via un message d’erreur, mais le déclenchement de cette erreur est bien réel.

Le cheminement de PHP dans ce cas-là est le suivant (qu’il s’agisse du gestionnaire d’erreur par défaut ou non) :

  1. Utilisation d’une entrée de tableau associatif qui n’existe pas
  2. Déclenchement d’une erreur
  3. Récupération de l’erreur par le gestionnaire d’erreur
  4. Test du niveau de reporting actuel via error_reporting()
  5. Affichage ou non d’un message d’erreur

On constate donc que ce chemin est certes le plus court pour le développeur, mais est relativement long pour PHP.

Certes, l’exécution d’un million d’itérations dure moins d’une seconde, il est donc tout à fait légitime de négliger cet aspect, mais gardons à l’esprit qu’un simple !empty($_GET[‘id’]) est trois fois plus rapide pour PHP, pour une longueur de code similaire et pour le même résultat.

En changeant cette habitude permissive, nous devrions pouvoir améliorer les performances générales de nos codes sources. Même si cela ne constitue qu’un infime gain, sur un environnement concurrentiel à très forte demande, cela peut contribuer à une meilleure qualité de service.

Note : comme le montre le code ci-dessous, les tests originaux ont été effectués sur un tableau vide. Les mêmes tests ont été effectués sur un tableau de 100000 entrées (ne contenant toujours pas l’entrée recherchée) et les valeurs sont similaires.

Code du test

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

    define('ITERATION_COUNT', 1000000);
    printf("Nombre d'itérations : %d\n\n", ITERATION_COUNT);

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if ($_GET['id'] != 0)
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Aucune protection : {$elapsed} ms\n";

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if (array_key_exists('id', $_GET) && $_GET['id'] != 0)
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Protection avec array_key_exists() : {$elapsed} ms\n";

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if (isset($_GET['id']) && $_GET['id'] != 0)
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Protection avec isset() : {$elapsed} ms\n";

    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        if (!empty($_GET['id']))
            echo 'ok';
    }
    $elapsed = microtime(true) - $start;
    echo "Protection avec empty() : {$elapsed} ms\n";
?>

Test de performances Java

Afin de valider une approche de développement pour un projet personnel, j’ai réalisé un petit test de performances en Java. Le but était de savoir quel mécanisme serait le plus rapide pour différencier des signaux d’événements. Les challengers étaient instanceof et le polymorphisme, les chaînes de caratères et les enums.

Le procédure utilisée est la suivante :

  • Remplir aléatoirement un tableau pour chaque mécanisme tout en gardant le même ordre des éléments pour tous les mécanismes
  • Tester successivement le parcours intégral de chaque tableau et évaluer la durée

Voilà les résultats du test :

Instances :
    A: 500537 - B: 500534 - C: 499560 - D: 499369 - Temps: 32
Strings (avec equals) :
    A: 500537 - B: 500534 - C: 499560 - D: 499369 - Temps: 58
Strings (avec ==) :
    A: 500537 - B: 500534 - C: 499560 - D: 499369 - Temps: 27
Enums (avec ==) :
    A: 500537 - B: 500534 - C: 499560 - D: 499369 - Temps: 26
Enums (switch) :
    A: 500537 - B: 500534 - C: 499560 - D: 499369 - Temps: 34

Les grands gagnants sont donc les enums et les chaînes de caractères évalués grâce à l’opérateur == .

Voici le code source utilisé pour le test :

import java.util.Random;

public class Test {
    public static class A { }
    public static class B { }
    public static class C { }
    public static class D { }

    public static int count = 2000000;

    public static enum TestEnum { A, B, C, D };

    public static void main(String[] args) {
        Random rng = new Random();

        Class<?>[] classValues = { A.class, B.class, C.class, D.class };
        Object[] instances = new Object[count];

        String aStr = "AAA";
        String bStr = "BBBBBB";
        String cStr = "CCCCCCCCC";
        String dStr = "DDDDDDDDDDDD";
        String[] stringValues = { "AAA", "BBBBBB", "CCCCCCCCC", "DDDDDDDDDDDD" };
        String[] strings = new String[count];

        TestEnum[] enumValues = { TestEnum.A, TestEnum.B, TestEnum.C, TestEnum.D };
        TestEnum[] enumRefs = new TestEnum[count];

        for (int i = 0; i < count; i++)
        {
            int rand = rng.nextInt(4);

            Class<?> c = classValues[rand];
            try {
                instances[i] = c.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }

            strings[i] = stringValues[rand];
            enumRefs[i] = enumValues[rand];
        }

        int a = 0, b = 0, c = 0, d = 0;
        long start = System.currentTimeMillis();
        for (Object o : instances)
        {
            if (o instanceof A)
                a++;
            else if (o instanceof B)
                b++;
            else if (o instanceof C)
                c++;
            else if (o instanceof D)
                d++;
         }
         long elapsed = System.currentTimeMillis() - start;

         System.out.println("Instances :");
         System.out.println(String.format("\tA: %d - B: %d - C: %d - D: %d - Temps: %d", a, b, c, d, elapsed));
         System.out.println();

         a = b = c = d = 0;
         start = System.currentTimeMillis();
         for (String s : strings)
         {
             if (s.equals(aStr))
                 a++;
             else if (s.equals(bStr))
                 b++;
             else if (s.equals(cStr))
                 c++;
             else if (s.equals(dStr))
                 d++;
         }
        elapsed = System.currentTimeMillis() - start;

        System.out.println("Strings (avec equals) :");
        System.out.println(String.format("\tA: %d - B: %d - C: %d - D: %d - Temps: %d", a, b, c, d, elapsed));
        System.out.println();

        a = b = c = d = 0;
        start = System.currentTimeMillis();
        for (String s : strings)
        {
            if (s == aStr)
                a++;
            else if (s == bStr)
                b++;
            else if (s == cStr)
                c++;
            else if (s == dStr)
                d++;
         }
         elapsed = System.currentTimeMillis() - start;

         System.out.println("Strings (avec ==) :");
         System.out.println(String.format("\tA: %d - B: %d - C: %d - D: %d - Temps: %d", a, b, c, d, elapsed));
         System.out.println();

         a = b = c = d = 0;
         start = System.currentTimeMillis();
         for (TestEnum e : enumRefs)
         {
             if (e == TestEnum.A)
                 a++;
             else if (e == TestEnum.B)
                 b++;
             else if (e == TestEnum.C)
                 c++;
             else if (e == TestEnum.D)
                 d++;
         }
         elapsed = System.currentTimeMillis() - start;

         System.out.println("Enums (avec ==) :");
         System.out.println(String.format("\tA: %d - B: %d - C: %d - D: %d - Temps: %d", a, b, c, d, elapsed));
         System.out.println();

         a = b = c = d = 0;
         start = System.currentTimeMillis();
         for (TestEnum e : enumRefs)
         {
             switch (e) {
                 case A:
                     a++;
                     break;
                 case B:
                     b++;
                     break;
                 case C:
                     c++;
                     break;
                 case D:
                     d++;
                     break;
             }
         }
         elapsed = System.currentTimeMillis() - start;

         System.out.println("Enums (switch) :");
         System.out.println(String.format("\tA: %d - B: %d - C: %d - D: %d - Temps: %d", a, b, c, d, elapsed));
         System.out.println();
    }
}