web-dev-qa-db-fra.com

Performance en boucle: stockage de la longueur d'un tableau dans une variable

Considérons deux versions de la même itération de boucle:

for (var i = 0; i < nodes.length; i++) {
    ...
}

et

var len = nodes.length;
for (var i = 0; i < len; i++) {
    ...
}

Cette dernière version est-elle de toute façon plus rapide que la précédente?

47
ducin

Mise à jour: 16/12/2015

Comme cette réponse semble encore susciter beaucoup de points de vue, je souhaitais réexaminer le problème au fur et à mesure que les navigateurs et les moteurs JS évoluent.

Plutôt que d'utiliser JSPerf, j'ai assemblé du code pour parcourir des tableaux à l'aide des deux méthodes mentionnées dans la question d'origine. J'ai mis le code dans des fonctions pour décomposer les fonctionnalités comme le ferait espérons-le dans une application réelle:

function getTestArray(numEntries) {
    var testArray = [];
    for(var i = 0; i < numEntries; i++) {
        testArray.Push(Math.random());
    }
    return testArray;
}

function testInVariable(testArray) {
    for (var i = 0; i < testArray.length; i++) {
        doSomethingAwesome(testArray[i]);
    }
}

function testInLoop(testArray) {
    var len = testArray.length;
    for (var i = 0; i < len; i++) {
        doSomethingAwesome(testArray[i]);
    }
}

function doSomethingAwesome(i) {
    return i + 2;
}

function runAndAverageTest(testToRun, testArray, numTimesToRun) {
    var totalTime = 0;
    for(var i = 0; i < numTimesToRun; i++) {
        var start = new Date();
        testToRun(testArray);
        var end = new Date();
        totalTime += (end - start);
    }
    return totalTime / numTimesToRun;
}

function runTests() {
    var smallTestArray = getTestArray(10000);
    var largeTestArray = getTestArray(10000000);

    var smallTestInLoop = runAndAverageTest(testInLoop, smallTestArray, 5);
    var largeTestInLoop = runAndAverageTest(testInLoop, largeTestArray, 5);
    var smallTestVariable = runAndAverageTest(testInVariable, smallTestArray, 5);
    var largeTestVariable = runAndAverageTest(testInVariable, largeTestArray, 5);

    console.log("Length in for statement (small array): " + smallTestInLoop + "ms");
    console.log("Length in for statement (large array): " + largeTestInLoop + "ms");
    console.log("Length in variable (small array): " + smallTestVariable + "ms");
    console.log("Length in variable (large array): " + largeTestVariable + "ms");
}

runTests();
runTests();
runTests();

Afin de réaliser un test aussi juste que possible, chaque test est exécuté 5 fois et les résultats sont moyennés. J'ai également exécuté l'ensemble du test, y compris la génération du tableau 3 fois. Les tests sur Chrome sur ma machine ont révélé que le temps nécessaire à l'utilisation de chaque méthode était presque identique.

Il est important de se rappeler que cet exemple est un peu un exemple jouet. En fait, la plupart des exemples pris hors du contexte de votre application sont susceptibles de générer des informations non fiables, car les autres actions de votre code peuvent affecter directement ou indirectement les performances. 

La ligne du bas

La meilleure façon de déterminer ce qui fonctionne le mieux pour votre application est de la tester vous-même! Les moteurs JS, la technologie de navigateur et la technologie de processeur évoluent constamment, il est donc impératif de toujours tester vos performances pour vous-même dans le contexte de votre application. Cela vaut également la peine de vous demander si vous avez un problème de performance. Si vous ne perdiez pas votre temps à faire des micro-optimisations imperceptibles pour l'utilisateur, il serait préférable de passer du temps à corriger des bugs et à ajouter des fonctionnalités, pour des utilisateurs plus satisfaits :).

Réponse originale:

Ce dernier serait légèrement plus rapide. La propriété length ne parcourt pas le tableau pour vérifier le nombre d'éléments, mais chaque fois qu'elle est appelée sur le tableau, ce tableau doit être déréférencé. En stockant la longueur dans une variable, la déréférence de tableau n'est pas nécessaire à chaque itération de la boucle.

Si vous êtes intéressé par les performances des différentes manières de boucler un tableau en javascript, jetez un coup d'œil à ceci jsperf

27
Neil Mountford

La réponse acceptée n’est pas correcte, car tout moteur approprié devrait pouvoir extraire la charge de propriété de la boucle avec des corps de boucle aussi simples. 

Voir this jsperf - au moins dans V8 il est intéressant de voir comment le stocker réellement dans une variable modifie l'allocation du registre - dans le code où la variable est utilisée, la variable sum est stockée dans la pile, tandis que Le array.length-in-a-loop-code est stocké dans un registre. Je suppose que quelque chose de similaire se passe dans SpiderMonkey et Opera également.

Selon l'auteur, JSPerf est utilisé incorrectement , 70% du temps. Ces réponses erronées, telles que données dans toutes les réponses, donnent des résultats trompeurs et les utilisateurs en tirent des conclusions erronées.

Certains drapeaux rouges placent du code dans les scénarios de test au lieu de fonctions, ne vérifiant pas l'exactitude du résultat, ni utilisant un mécanisme d'élimination du code mort, définissant la fonction dans la configuration ou les scénarios de test au lieu de globaux. Pour des raisons de cohérence, les fonctions de test avant tout point de repère aussi, afin que la compilation ne se produise pas dans la section chronométrée.

33
Esailija

Selon w3schools "Réduire l’activité dans les boucles" est considéré comme un code incorrect:

for (i = 0; i < arr.length; i++) {

Et ce qui suit est considéré comme un bon code:

var arrLength = arr.length;
for (i = 0; i < arrLength; i++) {

Comme l'accès au DOM est lent, voici ce qui a été écrit pour tester la théorie:

<!doctype html>

<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>my test scripts</title>
	</head>
	
	<body>
		<button onclick="initArray()">Init Large Array</button>
		<button onclick="iterateArraySlowly()">Iterate Large Array Slowly</button>
		<button onclick="iterateArrayQuickly()">Iterate Large Array Quickly</button>
		
		<p id="slow">Slow Time: </p>
		<p id="fast">Fast Time: </p>
		<p id="access"></p>


	
	<script>
	var myArray = [];
			
		function initArray(){
			var length = 1e6;
			var i;
			for(i = 0; i < length; i++) {
				myArray[i] = i;
			}
			console.log("array size: " + myArray.length);
		}
		
		function iterateArraySlowly() {
			var t0 = new Date().getTime();
			var slowText = "Slow Time: "
			var i, t;
			var Elm = document.getElementById("slow");
			for (i = 0; i < myArray.length; i++) {
				document.getElementById("access").innerHTML = "Value: " + i;
			}
			t = new Date().getTime() - t0;
			Elm.innerHTML = slowText + t + "ms";
		}
		
		function iterateArrayQuickly() {
			var t0 = new Date().getTime();
			var fastText = "Fast Time: "
			var i, t;
			var Elm = document.getElementById("fast");
			var length = myArray.length;
			for (i = 0; i < length; i++) {
				document.getElementById("access").innerHTML = "Value: " + i;
			}
			t = new Date().getTime() - t0;
			Elm.innerHTML = fastText + t + "ms";
		
		}
	</script>
	</body>
</html>

La chose intéressante est que l'itération exécutée en premier semble toujours l'emporter sur l'autre. Mais ce qui est considéré comme un "mauvais code" semble gagner la majorité du temps après que chacun d'eux ait été exécuté à quelques reprises. Peut-être que quelqu'un plus intelligent que moi peut expliquer pourquoi. Mais pour le moment, en ce qui concerne la syntaxe, je tiens à ce qui est le plus lisible pour moi:

for (i = 0; i < arr.length; i++) {
4
Tyler B. Long

si nodes est DOM nodeList, alors la deuxième boucle sera beaucoup plus rapide car dans la première boucle, vous parcourez le DOM (très coûteux) à chaque itération. jsperf

2
Molecular Man

Je crois que le nodes.length est déjà défini et n’est pas recalculé à chaque utilisation. Ainsi, le premier exemple serait plus rapide car il définissait une variable de moins. Bien que la différence serait imperceptible.

1
Grallen

Cela a toujours été le plus performant de tous les tests d'évaluation que j'ai utilisés.

for (i = 0, val; val = nodes[i]; i++) {
    doSomethingAwesome(val);
}
0
Adrian Bartholomew