Itérer efficacement sur les tableaux en Javascript

En Javascript, les tableaux sont une des structures de données les plus utilisées.


Il est très fréquent de devoir itérer sur le contenu d’un tableau et cette opération peut se révéler coûteuse lorsque l’on manipule des tableaux de plusieurs milliers d’éléments.

 

Comme vous le savez, chez Kuzzle nous portons une attention particulière aux performances du code que nous développons pour nos produits et encore plus particulièrement dans les "zones critiques de code" qui peuvent être exécutées potentiellement plusieurs centaines de fois par secondes.

 

Dans cet article, nous allons voir les avantages et les inconvénients de 3 méthodes d’itération sur les tableaux.

 

TLDR;

  • Utiliser Array.forEach dans les zones non-critiques de code
  • Utiliser for (let i; …; …) dans les zones critiques de code

Méthode 1: Array.forEach

La première méthode est d’utiliser la méthode Array.forEach.

 

Cette méthode prend en paramètre une fonction qui sera appelée avec chaque élément du tableau.

 

array.forEach(item => {
  const foobar = item * 2;
});

 

En Node.js 12, cette méthode offre de très bonnes performances et son utilisation dans le coeur de Kuzzle est recommandée pour les itérations sur les tableaux de zones non-critiques de code.

Méthode 2: for (const … of ...)

Une deuxième méthode repose sur les itérateurs de Javascript accessible grâce au symbole Symbol.iterator.

 

Si vous avez le temps, vous pouvez consulter le très bon article de Keith Cirkel sur les symboles: https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/

 

Elle permet d’itérer sur tous les éléments d’un tableau avec une syntaxe agréable à lire et simple à comprendre.

 

for (const item of array2) {
  const foobar = item * 2;
}

 

Cette méthode est l’ancienne méthode préférée dans le coeur pour parcourir les tableaux car en Node.js 6, la méthode Array.forEach était assez lente.

 

Ce n’est plus le cas en Node.js 12 et donc l’utilisation de for (const … of ...) est dépréciée dans le coeur.

Méthode 3: for (let i; …; ...)

Cette méthode parcours le tableau avec l’index de chaque élément.

Pour cela on utilise une boucle for et un nombre i représentant l’index de l’élément courant.

 

for (let i = 0; i < array.length; ++i) {
  const item = array[i];
 
  const foobar = item * 2;
}

 

C’est la méthode la plus rapide pour parcourir un tableau car c'est celle la plus proche de la syntax C++ qui est le langage utilisé par V8.

 

Cependant, elle rend le code plus complexe à comprendre ce qui a un impact sur la maintenabilité.

Elle n’est donc utilisée que dans les zones critiques de code.

Benchmark et conclusion

Le benchmark de ces méthodes est réalisé avec Node.js 12.13.0 que nous utilisons par défaut dans le core de Kuzzle:

 

$ node nodejs/loop-array.js

   for (let i) x            3,117,151 ops/sec ±0.66% (94 runs sampled)

   for (const of) x      817,769 ops/sec ±0.26% (98 runs sampled)

   Array.forEach x 2,297,760 ops/sec ±55.45% (93 runs sampled)

Fastest is while (for (let i))

 

 

 

Le benchmark des 3 méthodes précédentes est remporté par la la méthode for (let i; …; …) qui offre les meilleurs performances d’itération. Cependant du fait de sa complexité son usage est limité aux zones critiques de code uniquement.

 

Dans le reste du core de Kuzzle, nous utiliserons la méthode Array.forEach qui offre de bonnes performances et surtout une très bonne lisibilité du code.

 

Tout le code de ces benchmarks est disponible sur Gist.

Bonus: Créer un tableau à partir d'un tableau

Ce benchmark n'est pas vraiment comparable aux autres car elle sert uniquement à créer un tableau à partir d'un autre tableau.

Cependant c'est un cas qui se produit assez souvent pour réaliser un benchmark.

 

On a vu ensemble que c'était la méthode for (let i; …; …) qui apportait les meilleurs performances lors du parcours d'un tableau.

 

Dans le cas précis de la construction d'un tableau à partir d'un autre tableau, c'est la méthode Array.map qui est la plus efficace.

 

$ node loop-create-array.js

   foreach x             60,450 ops/sec ±8.91% (87 runs sampled)
  for (const of) x 112,013 ops/sec ±1.97% (84 runs sampled)
  for (let i) x        113,350 ops/sec ±2.63% (82 runs sampled)
  map x               153,657 ops/sec ±4.45% (78 runs sampled)
Fastest is map

 

Voir le code du benchmark sur Gist.

 

Adrien Maret

Postes associés