Benchmarks javacript

En programmation, on doit toujours garder en tête les moyens d'améliorer les preformances de son code : chercher l'algorithme le plus efficace, éviter d'accéder trop souvent au DOM (en javascript), utiliser les méthodes natives des objets quand elles sont disponibles, garder des données en « cache » quand elles doivent être réutilisées, etc. Mais la quête de performance peut parfois se heurter à d'autres principes de programmation, comme la factorisation du code. Petit exemple pratique...

Imaginons : nous avons un tableau myArray et nous cherchons à récupérer l'entrée qui obéit à un test quelconque. Voilà comment nous pourrions faire :

// Chercher une valeur dans un tableau
var value, searchedValue, k, len = myArray.length;
for (k = 0; k < len; k++) {
    value = myArray[k];
    if (/* some tests on value */) {
        searchedValue = value;
        break;
    }
}
Technique 1 : recherche directe.

Maintenant, nous pensons que ce type de recherche pourrait être utile ailleurs ; nous isolons donc la routine de recherche dans une fonction dédiée (findInArray), qui prend en paramètres le tableau à analyser et une fonction de rappel (cb). Cette fonction portera le test et devra retrouner un booléen indiquant si le test à réussi ou pas :

// Fonction générique de recherche dans un tableau
function findInArray (arr, cb) {
    var k, len = arr.length, pV;
    for (k = 0; k < len; k++) {
        pV = arr[k];
        if (cb(pV, k)) {
            return pV;
        }
    }
    return null;
}
// Fonction de test
function myTest (value, index) {
    return (/* some tests on value */) ? true : false;
}
// Chercher une valeur dans un tableau    
var searchedValue = findInArray(myArray, myTest);
Technique 2 : recherche par fonction dédiée.

Quand il s'agit de manipuler un tableau de plus d'une centaine d'entrées avec des tests un peu complexes, on envisage de mesurer la rapidité des opérations, surtout si elles se répétent plusieurs fois et qu'elles s'effectuent dans un contexte où d'autres traitements coûteux sont lancés. On en vient donc à la question suivante : laquelle de ces deux méthodes est la plus rapide ?

Intuitivement, on penserait que c'est la première, qui ne passe par aucun « intermédiaire » ; mais on pourrait considérer que la moindre efficacité de la seconde méthode serait acceptable, compensée par l'utilité de sa factorisation du code ; encore faut-il mesurer cette perte d'efficacité, et c'est à cela que sert un outil comme benchmarkjs. J'ai donc créer une série de tests et les résultats obtenus ont été plutôt… impressionnant :

findDirect x 507,829 ops/sec ±1.09% (61 runs sampled)
findInArray x 25,668 ops/sec ±0.40% (62 runs sampled)
Fastest is findDirect
Résultats retournés par benchmarkjs.

Si on en croit benchmarkjs, la méthode directe pourait effectuer presque 20 fois plus d'opérations par seconde !. Cela confirme l'intuition de départ, mais je ne m'attendais pas à une si grande différence.

Il faut évidemment mettre en perspective ces résultats, et tout autres de ce genre d'ailleurs, d'abord et surtout en fonction de l'utilisation de ce qui est testé : ici, la fonction ne sera jamais utilisé plus d'une dizaine de fois et, même si la condition de recherche est plus complexe que ce qui a été testé, les operations ne devraient pas prendre plus de quelques millisecondes, y compris avec la technique 2 ; l'utilisateur ne verra pas une grande différence.

Néanmoins, j'ai souvent tendance à factoriser mon code à la manière de la technique 2 ; savoir que la perte de preformance est loin d'être négligeable par rapport à des calculs plus directs est très utile à savoir.