v8 Implicazioni sulle prestazioni di JavaScript di const, let e var?


90

Indipendentemente dalle differenze funzionali, l'utilizzo delle nuove parole chiave "let" e "const" ha un impatto generico o specifico sul rendimento rispetto a "var"?

Dopo aver eseguito il programma:

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

.. I miei risultati sono stati i seguenti:

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}

Tuttavia, la discussione qui indicata sembra indicare un reale potenziale di differenze di prestazioni in determinati scenari: https://esdiscuss.org/topic/performance-concern-with-let-const


Penso che dipenda dall'uso, ad esempio letutilizzato in ambito di blocco dovrebbe essere più performante di var, che non ha ambito di blocco, ma solo ambito di funzione.
adeneo

Se posso chiedere, perché è @adeneo?
sean2078

1
@ sean2078 - se è necessario dichiarare una variabile che vive solo in un ambito di blocco, letlo farebbe, e quindi verrà raccolta dai rifiuti, mentre var, che è con ambito di funzione, non funzionerebbe necessariamente allo stesso modo. Ancora una volta penso che sia così specifico per l'utilizzo, che entrambi lete const possono essere più performanti, ma non lo sarebbero sempre.
adeneo

1
Sono confuso dal modo in cui il codice citato intende dimostrare qualsiasi differenza tra vare let: Non usa mai letaffatto.
TJ Crowder

1
Attualmente non lo è - solo const vs. var .. Originariamente tratto da gist.github.com/srikumarks/1431640 (credito a srikumarks), tuttavia è stata fatta richiesta di inserire il codice in questione
sean2078

Risposte:


123

TL; DR

In teoria , una versione non ottimizzata di questo ciclo:

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

potrebbe essere più lenta di una versione non ottimizzata dello stesso ciclo con var:

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

perché viene creata una variabile diversa i per ogni iterazione del ciclo con let, mentre ce n'è solo una iconvar .

Contro ciò si discute del fatto che il varè sollevato quindi è dichiarato al di fuori del loop mentre letè dichiarato solo all'interno del loop, il che può offrire un vantaggio di ottimizzazione.

In pratica , qui nel 2018, i moderni motori JavaScript fanno abbastanza introspezione del ciclo per sapere quando può ottimizzare quella differenza. (Anche prima di allora, è probabile che il tuo loop stesse facendo abbastanza lavoro da leteliminare comunque l'overhead correlato aggiuntivo . Ma ora non devi nemmeno preoccupartene.)

Fai attenzione ai benchmark sintetici in quanto sono estremamente facili da sbagliare e attivano gli ottimizzatori del motore JavaScript in modi che il codice reale non fa (sia in modo buono che cattivo). Tuttavia, se vuoi un benchmark sintetico, eccone uno:

Dice che non c'è alcuna differenza significativa in quel test sintetico su V8 ​​/ Chrome o SpiderMonkey / Firefox. (I test ripetuti in entrambi i browser hanno uno vincente, o l'altro vincente, e in entrambi i casi con un margine di errore.) Ma ancora una volta, è un benchmark sintetico, non il tuo codice. Preoccupati delle prestazioni del tuo codice quando e se il tuo codice ha un problema di prestazioni.

Per quanto riguarda lo stile, preferisco let per il vantaggio dell'ambito e il vantaggio della chiusura nei cicli se utilizzo la variabile loop in una chiusura.

Dettagli

L'importante differenza tra vare letin un forciclo è che ne iviene creato un diverso per ogni iterazione; affronta il classico problema delle "chiusure in loop":

La creazione del nuovo EnvironmentRecord per ogni corpo del ciclo ( collegamento alle specifiche ) è un lavoro e il lavoro richiede tempo, motivo per cui in teoria la letversione è più lenta dellavar versione.

Ma la differenza conta solo se crei una funzione (chiusura) all'interno del ciclo che utilizza i, come ho fatto nell'esempio di frammento eseguibile sopra. Altrimenti, la distinzione non può essere osservata e può essere ottimizzata.

Qui nel 2018, sembra che V8 (e SpiderMonkey in Firefox) stia facendo un'introspezione sufficiente da non avere costi di prestazioni in un ciclo che non fa uso della letsemantica di iterazione variabile di. Vedi questo test .


In alcuni casi, constpotrebbe fornire un'opportunità di ottimizzazione che varnon lo sarebbe, soprattutto per le variabili globali.

Il problema con una variabile globale è che è, beh, globale; qualsiasi codice ovunque potrebbe accedervi. Quindi, se dichiari una variabile con varche non intendi mai modificare (e non modificare mai il tuo codice), il motore non può presumere che non cambierà mai come risultato del codice caricato in un secondo momento o simile.

Con const, però, stai esplicitamente dicendo al motore che il valore non può cambiare¹. Quindi è libero di fare qualsiasi ottimizzazione desideri, inclusa l'emissione di un riferimento letterale invece di un riferimento variabile al codice che lo utilizza, sapendo che i valori non possono essere modificati.

¹ Ricorda che con gli oggetti, il valore è un riferimento all'oggetto, non all'oggetto stesso. Quindi con const o = {}, potresti cambiare lo stato dell'oggetto ( o.answer = 42), ma non puoi fare opunto a un nuovo oggetto (perché ciò richiederebbe la modifica del riferimento all'oggetto che contiene).


Quando si utilizzano leto constin varsituazioni simili, è improbabile che abbiano prestazioni diverse. Questa funzione dovrebbe avere esattamente le stesse prestazioni sia che tu usi varo let, ad esempio:

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

È tutto, ovviamente, improbabile che abbia importanza e qualcosa di cui preoccuparsi solo se e quando c'è un vero problema da risolvere.


Grazie per la risposta - Sono d'accordo, quindi per me stesso ho standardizzato sull'uso di var per le operazioni di loop come indicato nel tuo primo esempio di ciclo for, e let / const per tutte le altre dichiarazioni assumendo che la differenza di prestazioni sia essenzialmente inesistente come sembrerebbe il test delle prestazioni da indicare per ora. Forse in seguito verranno aggiunte ottimizzazioni su const. Cioè, a meno che qualcun altro non possa mostrare una differenza distinguibile tramite un esempio di codice.
sean2078

@ sean2078: lo uso anche letnell'esempio del ciclo. La differenza di prestazioni non vale la pena preoccuparsene nel caso del 99,999%.
TJ Crowder

2
A partire dalla metà del 2018, le versioni con let e var hanno la stessa velocità in Chrome, quindi ora non c'è più differenza.
Max

1
@ DanM .: Buone notizie, l'ottimizzazione sembra aver recuperato terreno, almeno in V8 e SpiderMonkey. :-)
TJ Crowder

1
Grazie. Giusto.
ipers

20

"LET" È MEGLIO NELLE DICHIARAZIONI DI LOOP

Con un semplice test (5 volte) in navigatore del genere:

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

Il tempo medio di esecuzione è superiore a 2,5 ms

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

Il tempo medio di esecuzione è superiore a 1,5 ms

Ho scoperto che il tempo di loop con let è migliore.


7
Eseguendolo in Firefox 65.0, ho ottenuto velocità medie di var=138.8mse let=4ms. Non è un errore di battitura, letè più di 30 volte più veloce in questo momento
Katamari

7
L'ho appena provato in Node v12.5. Ho scoperto che le velocità medie sono var=2.6mse let=1.0ms. Quindi lascia entrare Node è un po 'più del doppio più veloce.
Kane Hooper

2
Solo per sottolineare il solito punto che il test delle prestazioni è difficile in presenza di ottimizzatori: penso che il ciclo let sia stato completamente ottimizzato - lascia che esista solo all'interno del blocco e il loop non ha effetti collaterali e V8 è abbastanza intelligente da sapere che può basta rimuovere il blocco, quindi il ciclo. la dichiarazione var viene sollevata quindi non può saperlo. I tuoi loop così come sono ottengo 1ms / 0.4ms, tuttavia se per entrambi ho una variabile j (var o let) al di fuori del loop che è anche incrementata, ottengo 1ms / 1.5ms. cioè var loop nessuna modifica, let loop ora impiega più tempo.
Euan Smith

@KaneHooper - Se hai una differenza di cinque volte in Firefox, dovrà essere stato il corpo del loop vuoto a farlo. I loop reali non hanno corpi vuoti.
TJ Crowder

2
Attenzione ai benchmark sintetici , e in particolare a quelli con loop con corpi vuoti. Se effettivamente fai qualcosa in continuazione, questo benchmark sintetico (che, ancora una volta, fai attenzione! :-)) suggerisce che non ci sono differenze significative. Ne ho anche aggiunto uno alla mia risposta, quindi è sul posto (non come quei test jsPerf che continuavano a scomparire su di me. :-)). Le corse ripetute mostrano una vincita o l'altra vincente. Certamente niente di conclusivo.
TJ Crowder

10

TJ CrowderLa risposta di è davvero eccellente.

Ecco un'aggiunta di: "Quando potrei ottenere il massimo profitto dalla modifica delle dichiarazioni var esistenti in const?"

Ho scoperto che il maggior incremento delle prestazioni aveva a che fare con le funzioni "esportate".

Quindi, se i file A, B, R e Z stanno chiamando una funzione "utilità" nel file U che è comunemente usata attraverso la tua app, allora cambiare quella funzione di utilità su "const" e il riferimento del file genitore a un const può alcune prestazioni migliorate. Mi sembrava che non fosse misurabilmente più veloce, ma il consumo complessivo di memoria è stato ridotto di circa l'1-3% per la mia app grossolanamente monolitica di Frankenstein. Che se stai spendendo sacchi di denaro sul cloud o sul tuo server baremetal, potrebbe essere un buon motivo per spendere 30 minuti per esaminare e aggiornare alcune di quelle dichiarazioni var a const.

Mi rendo conto che se leggi come const, var, e lasci lavorare sotto le coperte probabilmente hai già concluso quanto sopra ... ma nel caso in cui ci hai "guardato" sopra: D.

Da quello che ricordo del benchmarking sul nodo v8.12.0 quando stavo effettuando l'aggiornamento, la mia app è passata da un consumo inattivo di ~ 240 MB di RAM a ~ 233 MB di RAM.


3

La risposta di TJ Crowder è molto buona ma:

  1. 'let' è fatto per rendere il codice più leggibile, non più potente
  2. in teoria let sarà più lento di var
  3. in pratica il compilatore non è in grado di risolvere completamente (analisi statica) un programma incompleto, quindi a volte perderà l'ottimizzazione
  4. in ogni caso l'utilizzo di 'let' richiederà più CPU per l'introspezione, il banco deve essere avviato quando google v8 inizia ad analizzare
  5. se l'introspezione fallisce, 'let' spingerà forte sul garbage collector V8, richiederà più iterazioni per liberarlo / riutilizzarlo. consumerà anche più RAM. la panchina deve tener conto di questi punti
  6. Google Closure trasformerà let in var ...

L'effetto del divario di prestazioni tra var e let può essere visto nel programma completo della vita reale e non su un singolo loop di base.

Ad ogni modo, usare let dove non è necessario, rende il codice meno leggibile.


Per interesse, quale teoria è letpiù lenta di var? Soprattutto visto il consenso nei commenti sulla risposta sopra che mostra che è più veloce?
JamesTheAwesomeDude
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.