web-dev-qa-db-ja.com

Forループのパフォーマンス:配列の長さを変数に保存する

同じループ反復の2つのバージョンを検討してください。

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

そして

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

後者のバージョンは前のバージョンよりも高速ですか?

50
ducin

更新:2015年12月16日

この答えにはまだ多くの意見があるように思えるので、ブラウザとJSエンジンが進化し続けるにつれて、問題を再検討したかったのです。

JSPerfを使用するのではなく、元の質問で述べた両方の方法を使用して配列をループするために コード をまとめました。実際のアプリケーションでうまくいけば行われるように、コードを関数に入れて機能を分解しました。

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

可能な限り公平なテストを達成するために、各テストは5回実行され、結果が平均化されます。また、アレイの生成を含むテスト全体を3回実行しました。私のマシンでChromeをテストしたところ、各メソッドの使用にかかった時間はほとんど同じでした。

この例はちょっとしたおもちゃの例であることに注意してください。実際、アプリケーションのコンテキストから取り出したほとんどの例は、コードが実行している他のことが直接または間接的にパフォーマンスに影響を与える可能性があるため、信頼できない情報をもたらす可能性があります。

ボトムライン

アプリケーションに最適なパフォーマンスを判断する最良の方法は、自分でテストすることです! JSエンジン、ブラウザテクノロジー、およびCPUテクノロジーは常に進化しているため、アプリケーションのコンテキスト内で常にパフォーマンスをテストすることが不可欠です。また、パフォーマンスの問題があるかどうかを自問する価値があります。そうしないと、ユーザーが感知できないほどの微調整を行うのに時間を費やさなければ、バグの修正と機能の追加に費やすことができ、ユーザーが幸せになります:)。

元の答え:

後者の方が少し速くなります。 lengthプロパティは、要素の数をチェックするために配列を繰り返し処理しませんが、配列で呼び出されるたびに、その配列を逆参照する必要があります。変数に長さを格納することにより、ループの反復ごとに配列の参照解除は不要です。

JavaScriptで配列をループするさまざまな方法のパフォーマンスに興味がある場合は、これを見てください jsperf

27
Neil Mountford

適切なエンジンは ループからプロパティの負荷を引き上げる が非常に単純なループ本体でできるため、受け入れられた答えは正しくありません。

this jsperf -少なくともV8では 興味深い 変数に実際に格納すると、レジスタの割り当てがどのように変化するか-変数が使用されるコードではsum変数はスタックに格納されますが、array.length- in-a-loop-codeそれはレジスタに保存されます。同様のことがSpiderMonkeyで発生しており、Operaも同様です。

著者によると、 JSPerfは誤って使用されています 、70%の時間です。ここでのすべての回答にあるこれらの壊れたjsperfsは、誤解を招く結果をもたらし、人々は間違った結論を導き出します。

一部のレッドフラグは、関数の代わりにコードをテストケースに配置し、結果の正確性をテストせず、デッドコードの除去を排除するメカニズムを使用せず、グローバルではなくセットアップまたはテストケースで関数を定義します。ベンチマークよりも前にテスト関数をセットアップして、時間指定セクションでコンパイルが行われないようにします。

33
Esailija

w3schools "ループ内のアクティビティを減らす" によると、以下は不正なコードと見なされます。

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

また、以下は適切なコードと見なされます。

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

DOMへのアクセスが遅いため、理論をテストするために次のように書かれています。

<!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>

興味深いのは、最初に実行された反復が常に他の反復に勝つように見えることです。しかし、「悪いコード」と見なされるものは、それぞれが数回実行された後、ほとんどの時間に勝つようです。おそらく私よりも賢い人が理由を説明できるでしょう。しかし、今のところ、構文に関しては、私にとって読みやすいものにこだわっています。

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

nodesDOM nodeListの場合、最初のループでは各反復でDOM(非常にコストがかかる)を検索するため、2番目のループははるかに高速になります。 jsperf

2
Molecular Man

nodes.lengthはすでに定義されており、使用するたびに再計算されていません。したがって、最初の例では、定義する変数が1つ少ないため、より高速になります。違いは目立ちませんが。

1
Grallen

これは、私が使用したベンチマークテストで常に最もパフォーマンスの高いものでした。

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