web-dev-qa-db-ja.com

Chromeで `for`ループ内で` let`を使用するのが遅いのはなぜですか?

メジャーアップデート。

Chromeメジャーリリースの新しい イグニッション+ターボファンエンジン for Chromeカナリア59は問題を解決しました。テストはまだです。 letvarで宣言されたループ変数の同じ時間を表示します。


元の(現在はミュート)質問。

Chromeのletループでforを使用すると、変数をループのスコープのすぐ外に移動する場合と比較して、実行速度が非常に遅くなります。

for(let i = 0; i < 1e6; i ++); 

2倍の時間がかかります

{ let i; for(i = 0; i < 1e6; i ++);}

何が起こっている?

スニペットは違いを示し、Chromeにのみ影響し、覚えている限りChromeサポートlet

var times = [0,0]; // hold total times
var count = 0;  // number of tests

function test(){
    var start = performance.now();
    for(let i = 0; i < 1e6; i += 1){};
    times[0] += performance.now()-start;
    setTimeout(test1,10)
}
function test1(){
    // this function is twice as quick as test on chrome
    var start = performance.now();
    {let i ; for(i = 0; i < 1e6; i += 1);}
    times[1] += performance.now()-start;
    setTimeout(test2,10)
}

// display results
function test2(){
    var tot =times[0]+times[1];
    time.textContent = tot.toFixed(3)  + "ms";
    time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3)  + "ms";
    time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
    if(count++ < 1000){;
        setTimeout(test,10);
    }
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
test2()

これに最初に遭遇したとき、それは新しく作成されたiのインスタンスが原因だと思いましたが、以下はそうではないことを示しています。

追加のlet宣言がランダムなiniで最適化され、kの不確定な値に追加される可能性を排除したので、コードスニペットを参照してください。

2番目のループカウンターも追加しましたp

var times = [0,0]; // hold total times
var count = 0;  // number of tests
var soak = 0; // to stop optimizations
function test(){
    var j;
    var k = time[1];
    var start = performance.now();
    for(let p =0, i = 0; i+p < 1e3; p++,i ++){j=Math.random(); j += i; k += j;};
    times[0] += performance.now()-start;
    soak += k;
    setTimeout(test1,10)
}
function test1(){
    // this function is twice as quick as test on chrome
    var k = time[1];
    var start = performance.now();
    {let p,i ; for(p = 0,i = 0; i+p < 1e3; p++, i ++){let j = Math.random(); j += i; k += j}}
    times[1] += performance.now()-start;
    soak += k;
    setTimeout(test2,10)
}

// display results
function test2(){
    var tot =times[0]+times[1];
    time.textContent = tot.toFixed(3)  + "ms";
    time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3)  + "ms";
    time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
    if(count++ < 1000){;
        setTimeout(test,10);
    }
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
test2()
23
Blindman67

メジャーアップデート。

Chromeメジャーリリースの新しい イグニッション+ターボファンエンジン for Chromeカナリア60.0.3087は問題を解決しました。テストでは、letvarで宣言されたループ変数の時間が同じであることが示されています。

サイドノート。私のテストコードはFunction.toString()を使用し、"function() {"ではなく"function () {"を返すためCanaryで失敗しました()] _過去のバージョン(regexpを使用して簡単に修正)と同じですが、Function.toSting()を使用する場合は潜在的な問題があります

Updateリンクを提供してくれたユーザー Dan。M に感謝 https://bugs.chromium.org/p/v8/issues/detail?id = 4762 (そしてヘッドアップ)これはこの問題についてもっと詳しく説明しています。


前の答え

オプティマイザーがオプトアウトしました。

この質問はしばらくの間私を困惑させ、2つの答えは明白な答えですが、時差が大きすぎて新しいスコープ変数と実行コンテキストを作成できないため、意味がありませんでした。

これを証明するために、私は答えを見つけました。

短い答え

宣言にletステートメントを含むforループは、オプティマイザーではサポートされていません。

Chrome profile of the two functions shown the slow function is not optimized Chromeバージョン55.0.2883.35ベータ版、Windows 10。

千の言葉に値する写真であり、最初に見るべき場所でした。

上記のプロファイルに関連する機能

_var time = [0,0]; // hold total times

function letInside(){
    var start = performance.now();

    for(let i = 0; i < 1e5; i += 1); // <- if you try this at home don't forget the ;

    time[0] += performance.now()-start;
    setTimeout(letOutside,10);
}

function letOutside(){ // this function is twice as quick as test on chrome
    var start = performance.now();

    {let i; for(i = 0; i < 1e5; i += 1)}

    time[1] += performance.now()-start;
    setTimeout(displayResults,10);
}
_

Chromeが主要なプレーヤーであり、ループカウンターのブロックされたスコープ変数はいたるところにあるため、パフォーマンスの高いコードが必要で、ブロックスコープ変数が重要であると感じている人はfunction{}(for(let i; i<2;i++}{...})//?WHY?しばらくの間考慮する必要があります代替構文であり、ループの外側でループカウンターを宣言します。

時間差はごくわずかですが、関数内のすべてのコードが_for(let i..._を使用して最適化されていないことを考慮すると、注意して使用する必要があります。


5
Blindman67

更新:2018年6月:Chromeは、この質問と回答が最初に投稿されたときよりもはるかに優れて最適化されています。ループ内で関数を作成していない場合は、letforを使用しても、それほど大きなペナルティはなくなります(作成している場合は、コストに見合うだけのメリットがあります)。


ループの反復ごとに新しいiが作成されるため、ループ内で作成されたクロージャはiその反復ので閉じます。これは、 forループ本体の評価 のアルゴリズムの仕様でカバーされています。これは、ループの反復ごとに新しい変数環境を作成することを説明しています。

例:

for (let i = 0; i < 5; ++i) {
  setTimeout(function() {
    console.log("i = " + i);
  }, i * 50);
}

// vs.
setTimeout(function() {
  let j;
  for (j = 0; j < 5; ++j) {
    setTimeout(function() {
      console.log("j = " + j);
    }, j * 50);
  }
}, 400);

それはもっと仕事です。 ループごとに新しいiが必要ない場合は、ループの外側でletを使用します。上記の更新を参照してください。エッジの場合以外は回避する必要はありません。

モジュール以外のすべてが実装されたので、V8はおそらく新しいものの最適化を改善すると期待できますが、機能が最初に最適化よりも優先されるべきであることは驚くべきことではありません。

他のエンジンがすでに最適化を行っているのは素晴らしいことですが、V8チームはまだそこに到達していないようです。 上記の更新を参照してください。

23
T.J. Crowder

@T.J。Crowderはすでにタイトルの質問に答えていますが、私はあなたの疑問に答えます。

これに最初に遭遇したとき、それは新しく作成されたiのインスタンスによるものだと思いましたが、以下はそうではないことを示しています。

実際には、i変数のスコープが新しく作成されたためです。これは(まだ)そのままでは最適化されていません 単純なブロックスコープよりも複雑です

追加のlet宣言がiniを使用してランダムに最適化され、kの不確定な値に追加される可能性を排除したため、2番目のコードスニペットを参照してください。

あなたの追加のlet j宣言

{let i; for (i = 0; i < 1e3; i ++) {let j = Math.random(); j += i; k += j;}}
// I'll ignore the `p` variable you had in your code

が最適化されました。これはオプティマイザーにとって非常に簡単なことです。ループ本体を次のように単純化することで、その変数を完全に回避できます。

k += Math.random() + i;

そこにクロージャを作成するか、evalまたは同様の忌まわしさを使用しない限り、スコープは実際には必要ありません。

そのようなクロージャを(デッドコードとして、オプティマイザーがそれを認識しないことを願って)導入し、ピットインする場合

{let i; for (i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}}

に対して

for (let i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}

次に、それらがほぼ同じ速度で実行されることがわかります。

var times = [0,0]; // hold total times
var count = 0;  // number of tests
var soak = 0; // to stop optimizations
function test1(){
    var k = time[1];
    var start = performance.now();
    {let i; for(i=0; i < 1e3; i++){ let j=Math.random(); k += j+i; function f() { j; }}}
    times[0] += performance.now()-start;
    soak += k;
    setTimeout(test2,10)
}
function test2(){
    var k = time[1];
    var start = performance.now();
    for(let i=0; i < 1e3; i++){ let j=Math.random(); k += j+i; function f() { j; }}
    times[1] += performance.now()-start;
    soak += k;
    setTimeout(display,10)
}

// display results
function display(){
    var tot =times[0]+times[1];
    time.textContent = tot.toFixed(3)  + "ms";
    time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3)  + "ms";
    time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
    if(count++ < 1000){
        setTimeout(test1,10);
    }
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
display();
2
Bergi