v8 Как влияет на производительность JavaScript const, let и var?

88

Независимо от функциональных различий, имеет ли использование новых ключевых слов 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

sean2078
источник
Я думаю, что это зависит от использования, например, 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, хотя вы явно сообщающие , что значение не может change¹. Таким образом, можно свободно выполнять любую оптимизацию, которую он хочет, включая создание литерала вместо ссылки на переменную для кода, использующего его, зная, что значения не могут быть изменены.

¹ Помните, что для объектов значение - это ссылка на объект, а не на сам объект. Таким образом const o = {}, вы можете изменить состояние объекта ( o.answer = 42), но не можете oуказать на новый объект (потому что для этого потребуется изменить ссылку на объект, которую он содержит).


При использовании letили constв других varподобных ситуациях они вряд ли будут иметь другую производительность. Эта функция должна иметь точно такую же производительность , используете ли вы varили let, например:

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

Все это, конечно, вряд ли имеет значение, и есть о чем беспокоиться, только если и когда есть реальная проблема, которую нужно решить.

TJ Crowder
источник
Спасибо за ответ - я согласен, поэтому для себя я стандартизировал использование var для операций цикла, как указано в вашем первом примере цикла, и let / const для всех других объявлений, предполагая, что разница в производительности по существу отсутствует, поскольку тест производительности может показаться указать пока. Возможно, позже будут добавлены оптимизации на const. То есть, если только кто-то другой не может показать заметную разницу на примере кода.
sean2078
@ sean2078: я тоже использую letв примере цикла. Разница в производительности не стоит беспокоиться в случае 99,999%.
TJ Crowder
2
По состоянию на середину 2018 года версии с let и var имеют одинаковую скорость в Chrome, поэтому теперь нет никакой разницы.
Макс
1
@DanM .: Хорошие новости, оптимизация, похоже, догнала, по крайней мере, в V8 и SpiderMonkey. :-)
TJ Crowder
1
Спасибо. Справедливо.
hypers
18

«ПУСТЬ» ЛУЧШЕ В ПЕТЛЕЙ ДЕКЛАРАЦИИ

С помощью простого теста (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это больше , чем 30x раз быстрее , прямо сейчас
Катамари
6
Я только что попробовал это в Node v12.5. Я обнаружил, что средние скорости равны var=2.6msи let=1.0ms. Так что пусть Node работает чуть более чем в два раза быстрее.
Кейн Хупер
2
Просто чтобы подчеркнуть, что тестирование производительности затруднено в присутствии оптимизаторов: я думаю, что цикл let оптимизируется полностью - let существует только внутри блока, и цикл не имеет побочных эффектов, а V8 достаточно умен, чтобы знать, что может просто снимите блок, затем петлю. объявление var поднято, поэтому он не может этого знать. Ваши циклы, как они есть, я получаю 1 мс / 0,4 мс, однако, если для обоих у меня есть переменная j (var или let) вне цикла, которая также увеличивается, я получаю 1 мс / 1,5 мс. т.е. цикл var без изменений, цикл let теперь занимает больше времени.
Юан Смит,
@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 и let работать под прикрытием, вы, вероятно, уже пришли к заключению выше ... но в случае, если вы "просмотрели" это: D.

Из того, что я помню о тестировании на узле v8.12.0, когда я делал обновление, мое приложение перешло с ~ 240 МБ ОЗУ в простоя на ~ 233 МБ ОЗУ.

Isaacdre
источник
2

Ответ TJ Crowder очень хорош, но:

  1. 'let' сделан, чтобы сделать код более читаемым, а не более мощным
  2. по идее пусть будет медленнее var
  3. на практике компилятор не может полностью решить (статический анализ) незавершенную программу, поэтому иногда он пропускает оптимизацию
  4. в любом случае использование 'let' потребует больше ЦП для самоанализа, стенд должен быть запущен, когда google v8 начинает анализировать
  5. Если интроспекция не удалась, 'let' будет сильно давить на сборщик мусора V8, для освобождения / повторного использования потребуется больше итераций. он также будет потреблять больше оперативной памяти. скамья должна учитывать эти моменты
  6. Google Closure преобразует let в var ...

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

В любом случае, использование let там, где это необязательно, сделает ваш код менее читаемым.

Майкл Вэлв
источник