implicações de desempenho do JavaScript v8 de const, let e var?

88

Independentemente das diferenças funcionais, o uso das novas palavras-chave 'let' e 'const' tem algum impacto generalizado ou específico no desempenho em relação a 'var'?

Depois de executar o programa:

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

.. Meus resultados foram os seguintes:

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}

No entanto, a discussão, conforme observado aqui, parece indicar um potencial real para diferenças de desempenho em certos cenários: https://esdiscuss.org/topic/performance-concern-with-let-const

sean2078
fonte
Eu acho que depende do uso, por exemplo letusado no escopo de bloco deve ter mais desempenho do que var, que não tem escopo de bloco, mas apenas escopo de função.
adeneo
Se posso perguntar, por que é @adeneo?
sean2078
1
@ sean2078 - se você precisar declarar uma variável que vive apenas em um escopo de bloco, letfaria isso e seria coletado como lixo, enquanto var, que tem escopo de função, não funcionaria necessariamente da mesma maneira. Novamente, eu acho que é tão específico para o uso, que ambos lete const podem ter mais desempenho, mas nem sempre seria.
adeneo
1
Estou confuso sobre como o código entre aspas pretende demonstrar qualquer diferença entre vare let: ele nunca usa let.
TJ Crowder
1
Atualmente não é - apenas const vs. var .. Originalmente originado de gist.github.com/srikumarks/1431640 (crédito para srikumarks), no entanto, a solicitação foi feita para colocar o código em questão
sean2078

Respostas:

116

TL; DR

Em teoria , uma versão não otimizada desse loop:

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

pode ser mais lento do que uma versão não otimizada do mesmo loop com var:

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

porque uma variável diferente i é criada para cada iteração do loop com let, enquanto há apenas uma icom var.

O que se argumenta contra isso é o fato de que varé içado de modo que seja declarado fora do loop, enquanto que letsó é declarado dentro do loop, o que pode oferecer uma vantagem de otimização.

Na prática , aqui em 2018, os mecanismos JavaScript modernos fazem introspecção suficiente do loop para saber quando ele pode otimizar essa diferença. (Mesmo antes disso, é provável que seu loop estivesse fazendo um trabalho suficiente para que a letsobrecarga relacionada ao adicional fosse eliminada de qualquer maneira. Mas agora você nem precisa se preocupar com isso.)

Cuidado com os benchmarks sintéticos , pois são extremamente fáceis de errar e acione os otimizadores do mecanismo JavaScript de maneiras que o código real não faz (boas e más). No entanto, se você quiser um benchmark sintético, aqui está um:

Ele diz que não há diferença significativa naquele teste sintético no V8 / Chrome ou no SpiderMonkey / Firefox. (Testes repetidos em ambos os navegadores têm um vencedor ou outro vencedor e, em ambos os casos, dentro de uma margem de erro.) Mas, novamente, é um benchmark sintético, não o seu código. Preocupe-se com o desempenho do seu código quando e se ele apresentar um problema de desempenho.

Por uma questão de estilo, eu prefiro leto benefício de escopo e o benefício de fechamento em loops se eu usar a variável de loop em um fechamento.

Detalhes

A diferença importante entre vare letem um forloop é que um diferente ié criado para cada iteração; trata do problema clássico de "fechamentos em loop":

Criar o novo EnvironmentRecord para cada corpo de loop ( link de especificação ) é trabalhoso e leva tempo, por isso, em teoria, a letversão é mais lenta do que a varversão.

Mas a diferença só importa se você criar uma função (encerramento) dentro do loop que usa i, como fiz no exemplo de snippet executável acima. Caso contrário, a distinção não pode ser observada e pode ser otimizada.

Aqui em 2018, parece que o V8 (e SpiderMonkey no Firefox) está fazendo introspecção suficiente para que não haja custo de desempenho em um loop que não use a letsemântica de variável por iteração. Veja este teste .


Em alguns casos, constpode fornecer uma oportunidade de otimização que varnão seria, especialmente para variáveis ​​globais.

O problema com uma variável global é que ela é, bem, global; qualquer código em qualquer lugar poderia acessá-lo. Portanto, se você declarar uma variável com vara qual nunca pretende alterar (e nunca alterar em seu código), o mecanismo não pode assumir que nunca vai mudar como resultado de código carregado posteriormente ou similar.

Com const, entretanto, você está dizendo explicitamente ao mecanismo que o valor não pode ser alterado¹. Portanto, é livre para fazer qualquer otimização que desejar, incluindo a emissão de um literal em vez de uma referência de variável para o código que o usa, sabendo que os valores não podem ser alterados.

¹ Lembre-se de que, com objetos, o valor é uma referência ao objeto, não o objeto em si. Assim const o = {}, você pode alterar o estado do objeto ( o.answer = 42), mas não pode oapontar para um novo objeto (porque isso exigiria a alteração da referência do objeto que ele contém).


Ao usar letou constem outras varsituações semelhantes, eles provavelmente não terão um desempenho diferente. Esta função deve ter exatamente o mesmo desempenho se você usar varou let, por exemplo:

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

É claro que é improvável que isso importe e algo com que se preocupar apenas se e quando houver um problema real para resolver.

TJ Crowder
fonte
Obrigado pela resposta - concordo, então, para mim mesmo, padronizei o uso de var para operações de loop, conforme observado em seu primeiro exemplo de loop, e let / const para todas as outras declarações, assumindo que a diferença de desempenho é essencialmente inexistente como o teste de desempenho pareceria para indicar por agora. Talvez mais tarde, otimizações em const sejam adicionadas. Isto é, a menos que outra pessoa possa mostrar uma diferença perceptível por meio do exemplo de código.
sean2078
@ sean2078: Eu uso leto exemplo de loop também. A diferença de desempenho simplesmente não vale a pena se preocupar com isso no case de 99,999%.
TJ Crowder
2
Em meados de 2018, as versões com let e var têm a mesma velocidade no Chrome, então agora não há mais diferença.
Máx.
1
@DanM .: Boas notícias, a otimização parece ter alcançado, pelo menos no V8 e no SpiderMonkey. :-)
TJ Crowder
1
Obrigado. Justo.
hypers
18

"LET" É MELHOR EM DECLARAÇÕES DE LOOP

Com um teste simples (5 vezes) no navegador assim:

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

O tempo médio de execução é de mais de 2,5 ms

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

O tempo médio de execução é de mais de 1,5 ms

Achei que o tempo de loop com let é melhor.

Amn
fonte
6
Executando isso no Firefox 65.0, obtive velocidades médias de var=138.8mse let=4ms. Isso não é um erro de digitação, leté mais de 30 vezes mais rápido agora
Katamari
6
Acabei de testar isso no Node v12.5. Eu descobri que as velocidades médias são var=2.6mse let=1.0ms. Então vamos deixar o Node é um pouco mais do que duas vezes mais rápido.
Kane Hooper
2
Só para deixar claro que o teste de desempenho é difícil na presença de otimizadores: acho que o loop let está sendo totalmente otimizado - let só existe dentro do bloco e o loop não tem efeito colateral e o V8 é inteligente o suficiente para saber que pode apenas remova o bloco e depois o loop. a declaração var é içada, então ele não pode saber disso. Seus loops como estão eu obtenho 1ms / 0,4ms, entretanto, se para ambos eu tiver uma variável j (var ou let) fora do loop que também é incrementada, eu obtenho 1ms / 1,5ms. ou seja, var loop sem alteração, deixe o loop demorar mais.
Euan Smith
@KaneHooper - Se você obteve uma diferença de cinco vezes no Firefox, deve ter sido o corpo de loop vazio que fez isso. Os loops reais não têm corpos vazios.
TJ Crowder
Cuidado com os benchmarks sintéticos , especialmente aqueles com loops com corpos vazios. Se você realmente fizer algo no loop, este benchmark sintético (que, novamente, cuidado! :-)) sugere que não há diferença significativa. Também adicionei um à minha resposta para que fique no local (não como aqueles testes jsPerf que sempre desaparecem de mim. :-)). As corridas repetidas mostram uma vitória ou a outra. Certamente nada conclusivo.
TJ Crowder
8

A resposta de TJ Crowder é excelente.

Aqui está um acréscimo de: "Quando eu obteria o maior retorno do meu investimento na edição de declarações var existentes para const?"

Descobri que o maior aumento de desempenho teve a ver com funções "exportadas".

Portanto, se os arquivos A, B, R e Z estão chamando uma função de "utilitário" no arquivo U que é comumente usada por meio de seu aplicativo, alternar essa função de utilitário para "const" e a referência do arquivo pai para um const pode ser algumas melhorias no desempenho. Pareceu-me que não era mensuravelmente mais rápido, mas o consumo geral de memória foi reduzido em cerca de 1-3% para o meu aplicativo Frankenstein grosseiramente monolítico. O que, se você está gastando muito dinheiro na nuvem ou no servidor baremetal, pode ser um bom motivo para gastar 30 minutos para vasculhar e atualizar algumas dessas declarações var para const.

Sei que se você ler como const, var e let trabalhar nos bastidores, você provavelmente já concluiu o acima ... mas no caso de você "dar uma olhada" nisso: D.

Pelo que me lembro do benchmarking no nó v8.12.0 quando estava fazendo a atualização, meu aplicativo passou de um consumo ocioso de ~ 240 MB de RAM para ~ 233 MB de RAM.

isaacdre
fonte
2

A resposta de TJ Crowder é muito boa, mas:

  1. 'let' é feito para tornar o código mais legível, não mais poderoso
  2. por teoria vamos ser mais lentos que var
  3. pela prática, o compilador não pode resolver completamente (análise estática) um programa incompleto, então às vezes ele perderá a otimização
  4. em qualquer caso, o uso de 'let' exigirá mais CPU para a introspecção, a bancada deve ser iniciada quando o google v8 começar a analisar
  5. se a introspecção falhar, 'let' forçará o coletor de lixo V8 e exigirá mais iteração para liberar / reutilizar. ele também consumirá mais RAM. o banco deve levar esses pontos em consideração
  6. O Google Closure transformará let in var ...

O efeito da lacuna de desempenho entre var e let pode ser visto no programa completo da vida real e não em um único loop básico.

De qualquer forma, usar let onde você não precisa, torna seu código menos legível.

Michael Valve
fonte