En JavaScript, j'ai remarqué que les performances de la boucle ES6 for ... of
étaient bien différentes de celles de la boucle for (start; stop; step)
traditionnelle.
const n = 10000;
const arr = Array(n).fill().map((e, i) => i); // [0, n)
console.log('n =', n);
let sum1 = 0;
console.time('for let i');
for (let i = 0; i < arr.length; i++) {
sum1 += arr[i];
}
console.timeEnd('for let i');
let sum2 = 0;
console.time('for of');
for (let v of arr) {
sum2 += v;
}
console.timeEnd('for of');
n = 10
for let i: 0.350ms
for of: 0.015ms
-----
n = 100
for let i: 0.354ms
for of: 0.023ms
-----
n = 1000
for let i: 0.429ms
for of: 0.111ms
-----
n = 10000
for let i: 1.048ms
for of: 2.138ms
-----
n = 100000
for let i: 9.452ms
for of: 13.644ms
(Testé avec Node.js v10.11.0)
Comme vous pouvez le constater, à mesure que n augmente, la vitesse de la boucle for-of diminue plus rapidement que celle de la boucle standard. Pourquoi la boucle for-of est-elle plus rapide pour les baies plus petites et plus lente pour les plus grandes?
Je suggérerais de jeter un coup d'œil au concept de { microbenchmarking , et aussi de se familiariser avec cette excellente réponse pour le microbenchmarking dans JS.
En bref, lorsque vous testez quelque chose de petit, comme des boucles for pour les synchronisations, vous pouvez facilement interférer avec votre logique de test avec autres processus en cours sous le capot . Par exemple, si vous permutez l'ordre d'exécution dans votre scénario de test pour que for of
soit exécuté avant for let
, vous remarquerez peut-être un changement de minutage surprenant pour des valeurs n
plus petites (spoiler: dans ce cas, for let
gagne la course pour n=10
et n=100
. )
Donc, la réponse à votre question pourquoi: for let
est plus lente dans votre liste, car elle est plus proche du début du programme et s’exécute sur une machine à froid 'plus froide', la réchauffant également pour l’instruction for let
qui en résulte. Plus la n
est importante, moins cet effet secondaire contribue au temps d'exécution mesuré.
C'est pourquoi les micro-critères impliquent l'exécution d'une série de tests identiques au lieu d'un seul - pour que les effets secondaires mineurs ne soient pas aussi importants à l'échelle supérieure.
De plus, notez que l'unité de mesure canonique de la performance des instructions est "le nombre total d'opérations par unité de temps", au lieu du temps absolu par un seul ensemble d'opérations.
Vous trouverez un exemple de micro-référence pour votre sujet ici _
Lors de l'analyse comparative de valeurs plus petites, les opérations de surcharge peuvent avoir un impact plus important sur le test.
Par exemple, si l’initialisation des variables et l’allocation de mémoire prennent 0,1 ms, ceci est négligeable avec n> 1000, mais significatif avec n = 10.
Dans ce cas, l'opérateur for/of permet au moteur V8 d'optimiser le fonctionnement de la boucle (en réduisant les frais généraux comme ci-dessus). Par exemple, il peut pré-charger les éléments du tableau sur la pile ou similaire.
L'opération for/let traite chaque élément indépendamment de l'ensemble du tableau et est plus explicite dans l'utilisation des variables (réduction de la quantité d'optimisation que le moteur peut effectuer).
Le for-of boucle seulement sur le data (comme les cellules d'un tableau), pas les objets eux-mêmes. Augmenter considérablement les performances.
L'autre boucle boucle sur objets. La boucle for-of simplifie le code internal.
Au lieu d’accéder aux adresses de mémoire (un nombre hexadécimal) du tableau, ce qui prend plus de temps pour les tableaux de taille plus grande, de taille n. Ces types de boucles conviennent mieux aux petites quantités.
La boucle for-of effectue une itération sur le data numérique et NON sur les objets, ce qui est beaucoup plus efficace pour effectuer une boucle sur une tonne d'éléments. Vous appliquez le type de boucle nécessaire à la situation spécifique de votre code.
C'est comme un cheval et une voiture. Le cheval est bon pour un voyage de moins de 100 milles et non de plus de 100 milles. Quand une voiture peut mieux faire ça.
Reportez-vous à ce lien pour plus d'informations .- https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/
Laissez-moi essayer d’expliquer, dans ce caspour est plus rapide que pour ... de car le fonctionnement est beaucoup plus simple, des explications détaillées sont disponibles ici :
pour (var VariableDeclarationList; Expression; Expression) Statement
for (let i = 0; i < arr.length; i++) {
sum1 += arr[i];
}
Alors qu'est-ce qui se passe est ici:
Mais les choses sont un peu différentes pour ... de
pour (var ForBinding dans Expression) Statement
let sum2 = 0;
console.time('for of');
for (let v of arr) {
sum2 += v;
}
Comme je le sais, for..of utilise un itérateur et des boucles spécifiques aux objets, ce qui prendra un peu plus de temps que for, donc les choses sont peu intéressantes ici. Si vous voulez en savoir plus à ce sujet, veuillez vérifier la différence de sémantique d'exécution:
pour sémantique d'exécution: ici
pour ... de sémantique d'exécution: ici
Ce cas n’est valable que pour les Symbol.iterators définis par l’utilisateur, mais les itérateurs intégrés doivent être optimisés pour fonctionner mieux dans un pour ... de au lieu d’un pour "merci pour @Aryter "
Alors, pourquoi votre test n'est pas précis? Parce que votre génération for ... de après for boucle donc moteur V8 est en train d’être optimisée.
Mon hypothèse de cette optimisation est liée aux propriétés JavaScript et rend le moteur V8 très variable de différence de vitesse lorsque l’objet est petit, ce que vous pouvez trouver plus en détail ici Propriétés rapides du moteur V8
L'espoir est plus clair maintenant ...