web-dev-qa-db-ja.com

const、let、varのv8 JavaScriptパフォーマンスへの影響

機能の違いに関係なく、新しいキーワード「let」と「const」を使用すると、「var」に比べてパフォーマンスに一般的または特定の影響がありますか?

プログラムを実行した後:

function timeit(f, N, S) {
    var start, timeTaken;
    var stats = {min: 1e50, max: 0, N: 0, sum: 0, sqsum: 0};
    var i;
    for (i = 0; i < S; ++i) {
        start = Date.now();
        f(N);
        timeTaken = Date.now() - start;

        stats.min = Math.min(timeTaken, stats.min);
        stats.max = Math.max(timeTaken, stats.max);
        stats.sum += timeTaken;
        stats.sqsum += timeTaken * timeTaken;
        stats.N++
    }

    var mean = stats.sum / stats.N;
    var sqmean = stats.sqsum / stats.N;

    return {min: stats.min, max: stats.max, mean: mean, spread: Math.sqrt(sqmean - mean * mean)};
}

var variable1 = 10;
var variable2 = 10;
var variable3 = 10;
var variable4 = 10;
var variable5 = 10;
var variable6 = 10;
var variable7 = 10;
var variable8 = 10;
var variable9 = 10;
var variable10 = 10;

function varAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += variable1;
        sum += variable2;
        sum += variable3;
        sum += variable4;
        sum += variable5;
        sum += variable6;
        sum += variable7;
        sum += variable8;
        sum += variable9;
        sum += variable10;
    }
    return sum;
}

const constant1 = 10;
const constant2 = 10;
const constant3 = 10;
const constant4 = 10;
const constant5 = 10;
const constant6 = 10;
const constant7 = 10;
const constant8 = 10;
const constant9 = 10;
const constant10 = 10;

function constAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += constant1;
        sum += constant2;
        sum += constant3;
        sum += constant4;
        sum += constant5;
        sum += constant6;
        sum += constant7;
        sum += constant8;
        sum += constant9;
        sum += constant10;
    }
    return sum;
}


function control(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
    }
    return sum;
}

console.log("ctl = " + JSON.stringify(timeit(control, 10000000, 50)));
console.log("con = " + JSON.stringify(timeit(constAccess, 10000000, 50)));
console.log("var = " + JSON.stringify(timeit(varAccess, 10000000, 50)));

..私の結果は次のとおりです。

ctl = {"min":101,"max":117,"mean":108.34,"spread":4.145407097016924}
con = {"min":107,"max":572,"mean":435.7,"spread":169.4998820058587}
var = {"min":103,"max":608,"mean":439.82,"spread":176.44417700791374}

ただし、ここで説明する議論は、特定のシナリオでのパフォーマンスの違いの本当の可能性を示しているようです: https://esdiscuss.org/topic/performance-concern-with-let-const

62
sean2078

TL; DR

理論上、このループの最適化されていないバージョン:

for (let i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

varを使用した同じループの最適化されていないバージョンよりも遅くなります。

for (var i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

differenti変数は、letを使用するループの反復ごとに作成されるのに対し、iを使用するvarは1つしかないためです。

実際には、ここ2018年に、V8はループの内省を十分に行い、その差をいつ最適化できるかを知ります。 (それ以前でも、ループが十分な処理を行っていたため、追加のlet関連のオーバーヘッドが洗い流された可能性があります。しかし、今では心配する必要さえありません。)

詳細

varループのletforの重要な違いは、反復ごとに異なるiが作成されることです。従来の「ループ内のクロージャ」問題に対処します。

function usingVar() {
  for (var i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("var's i: " + i);
    }, 0);
  }
}
function usingLet() {
  for (let i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("let's i: " + i);
    }, 0);
  }
}
usingVar();
setTimeout(usingLet, 20);

ループ本体ごとに新しいEnvironmentRecordを作成する( spec link )作業が必要であり、作業に時間がかかるため、理論的にはletバージョンがvarバージョンよりも遅いのはこのためです。

ただし、上記の実行可能なスニペットの例で行ったように、iを使用するループ内で関数(クロージャー)を作成する場合にのみ、違いは重要です。それ以外の場合、区別は観察できず、最適化して削除できます。

ここ2018年、V8(およびFirefoxのSpiderMonkey)は、letの変数ごとのセマンティクスを使用しないループでパフォーマンスコストがないことを十分に内省しているようです。 this jsPerf test を参照してください。


場合によっては、constは、varにはない最適化の機会を、特にグローバル変数には提供する可能性があります。

グローバル変数の問題は、それがグローバルであることです。 anyコードanywhereはアクセスできます。したがって、varで変数を宣言する場合、決して変更するつもりはない(そしてコードを変更しない)場合、エンジンは、後でロードされるコードまたは同様のコードの結果として、変数が決して変更されないことを想定できません。

ただし、constを使用すると、値を変更できないことをエンジンに明示的に伝えます¹。したがって、値を変更できないことを認識して、それを使用するコードへの変数参照の代わりにリテラルを発行するなど、必要な最適化を自由に行うことができます。

¹オブジェクトの場合、値はaであることに注意してくださいオブジェクト自体ではなく、オブジェクトを参照してください。したがって、const o = {}を使用すると、オブジェクトの状態を変更できます(o.answer = 42)が、oが新しいオブジェクトを指すようにすることはできません(含まれるオブジェクト参照を変更する必要があるため)。


letまたはconstを他のvarのような状況で使用する場合、パフォーマンスが異なる可能性はほとんどありません。この関数は、varを使用する場合でもletを使用する場合でも、まったく同じパフォーマンスを持つ必要があります。たとえば、

function foo() {
    var i = 0;
    while (Math.random() < 0.5) {
        ++i;
    }
    return i;
}

もちろん、すべてが問題になる可能性は低く、解決すべき実際の問題がある場合にのみ心配する必要があります。

80
T.J. Crowder

"LET" ISループ宣言でより良い

そのようなナビゲータでの簡単なテスト(5回)で:

// WITH VAR
console.time("var-time")
for(var i = 0; i < 500000; i++){}
console.timeEnd("var-time")

平均実行時間は2.5ms以上です

// WITH LET
console.time("let-time")
for(let i = 0; i < 500000; i++){}
console.timeEnd("let-time")

平均実行時間は1.5ms以上です

Letを使用したループ時間の方が優れていることがわかりました。

6
Amn

T.J。Crowderの答えはとても素晴らしいです。

「既存のvar宣言をconstに編集することで、いつまでに最大限の価値を得ることができますか?」

パフォーマンスの最大の向上は、「エクスポートされた」関数に関係していることがわかりました。

したがって、ファイルA、B、R、およびZが、アプリで一般的に使用されるファイルUの「ユーティリティ」関数を呼び出し、そのユーティリティ関数を「const」に切り替え、constへの親ファイル参照がeakできる場合いくつかの改善されたパフォーマンス。かなり高速ではないように思えましたが、全体的なメモリ消費は、私の非常にモノリシックなFrankenstein版のアプリで約1〜3%削減されました。クラウドまたはベアメタルサーバーに大量の現金を費やしている場合、これらのvar宣言の一部を調べてconstに更新するのに30分を費やすのが妥当な理由になります。

Const、var、およびカバーの下で作業する方法を読んでいると、おそらく上記をすでに完了していることがわかります...しかし、あなたがそれを見た場合:D。

更新を行っていたときのノードv8.12.0でのベンチマークの記憶から、私のアプリはアイドル消費が最大240MB RAMから最大233MB RAMになりました。

5
isaacdre

T.J.クラウダーの答えは非常に良いですが、

  1. 「let」は、コードをより読みやすくするために作成されたもので、強力ではありません
  2. 理論的にはletはvarよりも遅くなります
  3. 実際には、コンパイラは未完成のプログラムを完全に解決することはできません(静的解析)ので、いつかは最適化を逃します
  4. いずれの場合でも「let」を使用すると、イントロスペクションにより多くのCPUが必要になり、Google v8が解析を開始するときにベンチを開始する必要があります
  5. イントロスペクションが失敗すると、「let」がV8ガベージコレクターを強くプッシュし、解放/再利用するためにさらに反復が必要になります。また、より多くのRAMを消費します。ベンチはこれらの点を考慮する必要があります
  6. Google Closureはletを変数に変換します...

Varとletのパフォーマンスのギャップの影響は、単一の基本的なループではなく、実際の完全なプログラムで見ることができます。

とにかく、必要のないところでletを使用すると、コードが読みにくくなります。

0
Michael Valve