v8 Наслідки продуктивності JavaScript const, let і var?


88

Незалежно від функціональних відмінностей, чи має використання нових ключових слів "дозволити" та "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


Я думаю, що це залежить від використання, наприклад, що letвикористовується в області блоку, має бути більш продуктивним, ніж var, який не має блоку, а лише область функцій.
adeneo

Якщо я можу запитати, чому це @adeneo?
sean2078

1
@ sean2078 - якщо вам потрібно оголосити змінну, яка живе лише в області блоку, letзробить це, а потім буде збирати сміття, тоді як var, що має функцію, не буде працювати однаково. Знову ж думаю, це настільки специфічно для використання, що і те, letі інше const може бути більш продуктивним, але не завжди.
adeneo

1
Мене бентежить те, як цитований код призначений для демонстрації будь-якої різниці між varі let: Він взагалі ніколи не використовує let.
TJ Crowder,

1
В даний час це не так - лише const проти var. Першоджерело з gist.github.com/srikumarks/1431640 (кредит srikumarks), однак було зроблено запит поставити під сумнів код
sean2078,

Відповіді:


116

TL; DR

Теоретично , неоптимізована версія цього циклу:

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

може бути повільнішим, ніж неоптимізована версія того ж циклу з var:

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

так як інша i змінна створюється для кожної ітерації циклу з let, в той час як є тільки одна iз var.

Аргументом проти цього є той факт, що varпіднятий, тому він оголошується поза циклом, тоді як letоголошується лише в циклі, що може запропонувати перевагу оптимізації.

На практиці тут, у 2018 році, сучасні двигуни JavaScript роблять достатньо самоаналізу циклу, щоб знати, коли він може оптимізувати цю різницю. (Навіть до того, ймовірно, ваш цикл робив достатньо роботи, щоб додаткові letпов’язані накладні витрати в будь-якому випадку були розмиті. Але тепер вам навіть не потрібно про це турбуватися.)

Остерігайтеся синтетичних тестів, оскільки їх надзвичайно легко помилитися, і запускайте оптимізатори двигуна JavaScript так, як цього не робить реальний код (як хороший, так і поганий). Однак, якщо ви хочете синтетичний орієнтир, ось один:

Там сказано, що в цьому синтетичному тесті немає істотної різниці ні на V8 / Chrome, ні на SpiderMonkey / Firefox. (Повторні тести в обох браузерах мають один або інший виграш, і в обох випадках з похибкою.) Але знову ж таки, це синтетичний орієнтир, а не ваш код. Турбуйтеся про продуктивність вашого коду, коли і якщо ваш код має проблеми з продуктивністю.

Що стосується стилю, я віддаю перевагу перевазі letмасштабування та перевазі замикання в циклах, якщо я використовую змінну циклу в закритті.

Деталі

Важливою відмінністю між циклом varта letу ньому forє те, що iдля кожної ітерації створюється різний ; він розглядає класичну проблему "закриття в циклі":

Створення нового EnvironmentRecord для кожного тіла циклу ( специфікаційне посилання ) є роботою, і робота вимагає часу, тому теоретично letверсія є повільнішою за varверсію.

Але різниця має значення лише в тому випадку, якщо ви створюєте функцію (закриття) у циклі, який використовує i, як я це зробив у тому прикладі запущеного фрагмента вище. В іншому випадку розбіжності неможливо спостерігати і можна оптимізувати.

У 2018 році, схоже, V8 (і SpiderMonkey у Firefox) робить достатню інтроспекцію, щоб у циклі не було витрат на продуктивність, яка не використовує letсемантику змінної на ітерацію змінної. Дивіться цей тест .


У деяких випадках constцілком може надати можливість для оптимізації, яка varне буде, особливо для глобальних змінних.

Проблема глобальної змінної полягає в тому, що вона, ну, глобальна; будь-який код де завгодно міг отримати до нього доступ. Отже, якщо ви оголосите змінну, varяку ви ніколи не збираєтеся змінювати (і ніколи не змінюєте свій код), движок не може припустити, що він ніколи не зміниться в результаті завантаження коду пізніше або подібного.

Однак const, якщо ви прямо говорите двигуну, що значення не може змінитися¹. Тож ви можете безкоштовно виконувати будь-яку оптимізацію, включаючи випуск літералу замість посилання змінної на код, що використовує його, знаючи, що значення не можна змінювати.

Пам'ятайте, що для об'єктів значення є посиланням на об'єкт, а не на сам об'єкт. Отже, з const o = {}, ви можете змінити стан об'єкта ( o.answer = 42), але ви не можете oвказати на новий об'єкт (оскільки для цього потрібно буде змінити посилання на об'єкт, яке він містить).


При використанні letабо constв інших varподібних ситуаціях вони, ймовірно, не матимуть різної продуктивності. Ця функція повинна мати однакову продуктивність, незалежно від того, чи використовуєте ви, varабо let, наприклад:

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

Це, звичайно, навряд чи має значення, і щось, про що слід турбуватися, лише якщо і коли є реальна проблема, яку потрібно вирішити.


Дякую за відповідь - я погоджуюсь, тому для себе я стандартизував використання var для операцій циклу, як зазначено у вашому прикладі 1-го циклу for, і дозволив / const для всіх інших заяв, припускаючи, що різниця в продуктивності по суті не існує, як здається тест продуктивності вказати поки. Можливо, пізніше буде додано оптимізацію const. Тобто, якщо хтось інший не може показати помітну різницю на прикладі коду.
sean2078

@ sean2078: Я також використовую letприклад циклу. Різниця в продуктивності просто не варта про це турбуватися у випадку 99,999%.
TJ Crowder,

2
Починаючи з середини 2018 року, версії з let і var мають однакову швидкість у Chrome, тому зараз різниці вже немає.
Макс

1
@DanM .: Хороші новини, схоже, оптимізація наздогнала, принаймні у V8 та SpiderMonkey. :-)
TJ Crowder,

1
Дякую. Справедливо.
гіпермери

18

"LET" ЛІПШЕ В ДЕКЛАРАЦІЯХ ЦИКЛУ

За допомогою простого тесту (5 разів) в навігаторі, як це:

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

Середній час виконання - понад 2,5 мс

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

Середній час виконання - понад 1,5 мс

Я виявив, що час циклу з let є кращим.


6
Запустивши це у Firefox 65.0, я отримав середню швидкість var=138.8msі let=4ms. Це не друкарська помилка, letзараз це в 30 разів швидше
Катамарі,

6
Я щойно випробував це у Node v12.5. Я знайшов середні швидкості var=2.6msі let=1.0ms. Тож вхід у Node є трохи більше ніж удвічі швидшим.
Кейн Хупер,

2
Тільки для того, щоб звичайно сказати, що тестування продуктивності важко в присутності оптимізаторів: я думаю, що цикл let оптимізується повністю - нехай існує лише всередині блоку, і цикл не має побічних ефектів, і V8 досить розумний, щоб знати, що він може просто видаліть блок, потім цикл. декларація var піднята, тому вона не може цього знати. Ваші петлі, якими вони є, я отримую 1 мс / 0,4 мс, однак якщо для обох у мене є змінна j (var або let) поза циклом, яка також збільшується, то я отримую 1 мс / 1,5 мс. тобто цикл var не змінюється, нехай цикл триває довше.
Юан Сміт

@KaneHooper - Якщо у вас є п’ятикратна різниця у Firefox, це, мабуть, було порожнім тілом циклу. Справжні цикли не мають порожніх тіл.
TJ Crowder,

Остерігайтеся синтетичних тестів , і зокрема тих, що мають петлі з порожнім тілом. Якщо ви насправді щось робите в циклі, цей синтетичний орієнтир (якого, знову ж таки, остерігайтеся! :-)) припускає, що істотної різниці немає. Я також додав одну до своєї відповіді, щоб вона була на сайті (не як ті тести jsPerf, які постійно зникали в мене. :-)). Повторні пробіги показують виграш одного або іншого виграшу. Звичайно, нічого остаточного.
TJ Crowder,

8

Відповідь TJ Crowder настільки відмінна.

Ось доповнення: "Коли я отримаю найбільший удар, щоб за редагування існуючих декларацій var до const?"

Я виявив, що найбільший приріст продуктивності пов’язаний із функціями "експорту".

Отже, якщо файли A, B, R та Z викликають функцію "утиліта" у файлі U, яка зазвичай використовується у вашому додатку, тоді перемикання цієї функції утиліти на "const" і посилання батьківського файлу на const може зникнути покращити продуктивність. Мені здавалося, що це було не помірно швидше, але загальне споживання пам'яті зменшилось приблизно на 1-3% для мого дуже монолітного додатку Франкенштейна. Що якщо ви витрачаєте мішки з готівкою на хмарі або на сервері без металу, це може бути вагомою причиною витратити 30 хвилин на прочісування та оновлення деяких з цих декларацій var до const.

Я розумію, що якщо ви прочитаєте, як const, var і дозволяєте працювати під ковдрами, ви, напевно, вже зробили висновок із вищезазначеного ... але на випадок, якщо ви "заглянули" над цим: D.

З того, що я пам'ятаю про тестування на node v8.12.0, коли я робив оновлення, мій додаток перейшов із простою споживання ~ 240 Мб оперативної пам'яті до ~ 233 Мб оперативної пам'яті.


2

Відповідь TJ Crowder дуже хороша, але:

  1. 'let' зроблено, щоб зробити код більш читабельним, а не більш потужним
  2. теоретично нехай буде повільніше, ніж var
  3. на практиці компілятор не може повністю вирішити (статичний аналіз) незавершену програму, тому колись він пропустить оптимізацію
  4. у будь-якому випадку використання функції "дозвольте" вимагатиме більше процесора для самоаналізу, лава повинна бути запущена, коли Google v8 почне аналізувати
  5. якщо самоаналіз не вдається, "нехай" буде сильно натискати на збирач сміття V8, для звільнення / повторного використання знадобиться більше ітерацій. він також споживатиме більше оперативної пам'яті. лава повинна враховувати ці моменти
  6. Закриття Google перетворить путівку в ...

Ефект розриву продуктивності між var і let можна побачити в реальній повній програмі, а не в одному базовому циклі.

У будь-якому випадку, використання let, де вам не потрібно, робить ваш код менш читабельним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.