I loop sono davvero più veloci al contrario?


268

L'ho sentito parecchie volte. I loop JavaScript sono molto più veloci quando si conta indietro? Se è così, perché? Ho visto alcuni esempi di suite di test che mostrano che i loop invertiti sono più veloci, ma non riesco a trovare alcuna spiegazione sul perché!

Suppongo sia perché il ciclo non deve più valutare una proprietà ogni volta che controlla se è finito e verifica solo il valore numerico finale.

ie

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}

6
hehe. ci vorrà indefinitamente. prova io--
StampedeXV,

5
Il forciclo all'indietro è più veloce perché la variabile di controllo del ciclo con limite superiore (hehe, limite inferiore) non deve essere definita o recuperata da un oggetto; è uno zero costante.
Josh Stodola,

88
Non c'è vera differenza . I costrutti a ciclo nativo saranno sempre molto veloci . Non preoccuparti delle loro prestazioni.
James Allardice,

24
@Afshin: per domande come questa, ti preghiamo di mostrarci gli articoli a cui ti riferisci.
Razze di leggerezza in orbita

22
La differenza è importante soprattutto per dispositivi di fascia bassa e alimentati a batteria. La differenza è che con i-- si confronta a 0 per la fine del ciclo, mentre con i ++ si confronta con il numero> 0. Credo che la differenza di prestazioni fosse qualcosa come 20 nanosecondi (qualcosa come cmp ax, 0 vs. cmp ax , bx) - che non è altro che se
esegui un

Risposte:


903

Non i--è più veloce di i++. In realtà, sono entrambi ugualmente veloci.

Ciò che richiede tempo in circuiti ascendenti è valutare, per ciascuno i, la dimensione dell'array. In questo ciclo:

for(var i = array.length; i--;)

Valuta .lengthuna sola volta, quando dichiari i, mentre per questo ciclo

for(var i = 1; i <= array.length; i++)

si valuta .lengthogni volta che si incrementa i, quando si controlla se i <= array.length.

Nella maggior parte dei casi non dovresti nemmeno preoccuparti di questo tipo di ottimizzazione .


23
vale la pena introdurre una variabile per array.length e usarla nella testa del ciclo for?
ragatskynet,

39
@ragatskynet: No, a meno che tu non stia impostando un benchmark e desideri fare un punto.
Jon,

29
@ragatskynet Dipende: sarà più veloce valutare .lengthun certo numero di volte o dichiarare e definire una nuova variabile? Nella maggior parte dei casi, si tratta di un'ottimizzazione prematura (e sbagliata), a meno che non lengthsia molto costoso da valutare.
alestanis,

10
@ Dr.Dredel: non è il confronto, è la valutazione. 0è più veloce da valutare diarray.length . Bene, presumibilmente.
Razze di leggerezza in orbita

22
Ciò che vale la pena menzionare è che stiamo parlando di linguaggi interpretati come Ruby, Python. I linguaggi compilati, ad esempio Java, hanno ottimizzazioni a livello di compilatore che "attenueranno" queste differenze al punto che non importa se .lengthè in dichiarazione for loopo meno.
Haris Krajina,

198

Questo ragazzo ha confrontato molti loop in javascript, in molti browser. Ha anche una suite di test in modo da poterli eseguire da soli.

In tutti i casi (a meno che non ne abbia perso uno nella mia lettura) il ciclo più veloce è stato:

var i = arr.length; //or 10
while(i--)
{
  //...
}

Bello :) Ma non esiste un "for" -loop testato all'indietro ... Ma i loop for menzionati da peirix o searlea dovrebbero essere praticamente gli stessi del ciclo "while" con "i--" come condizione. E quello è stato il ciclo più veloce testato.
SvenFinke,

11
Interessante, ma mi chiedo se il pre-decremento sarebbe ancora più veloce. Dal momento che non dovrà memorizzare il valore intermedio di i.
Tvanfosson,

Se ricordo bene, il mio prof del corso hardware ha detto che quel test per 0 o meno 0 è il "calcolo" più veloce possibile. Nel frattempo (i--) il test è sempre un test per 0. Forse è per questo che è più veloce?
StampedeXV,

3
@tvanfosson se pre-decrementi --idevi usare var current = arr[i-1];all'interno del loop o sarà spento di uno ...
DaveAlger

2
Ho sentito che i-- può essere più veloce di --i perché nel secondo caso il processore deve decrementare e quindi testare rispetto al nuovo valore (c'è una dipendenza dai dati tra le istruzioni), mentre nel primo caso il processore può prova il valore esistente e decrementa il valore qualche tempo dopo. Non sono sicuro se questo si applica a JavaScript o solo a un codice C di livello molto basso.
marcus

128

Cerco di dare un quadro generale con questa risposta.

I seguenti pensieri tra parentesi erano la mia convinzione fino a quando non ho appena testato il problema di recente:

[[In termini di linguaggi di basso livello come C / C ++ , il codice viene compilato in modo che il processore disponga di un comando di salto condizionale speciale quando una variabile è zero (o diverso da zero).
Inoltre, se ti interessa questa ottimizzazione, potresti andare ++iinvece i++, perché ++iè un comando a processore singolo mentre i++significa j=i+1, i=j.]]

I cicli veramente veloci possono essere fatti srotolandoli:

for(i=800000;i>0;--i)
    do_it(i);

Può essere molto più lento di

for(i=800000;i>0;i-=8)
{
    do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}

ma le ragioni possono essere piuttosto complicate (solo per citare, ci sono i problemi di preelaborazione dei comandi del processore e gestione della cache nel gioco).

In termini di lingue di alto livello , come JavaScript come richiesto, è possibile ottimizzare le cose se si fa affidamento su librerie, funzioni integrate per il looping. Lascia che decidano come è meglio farlo.

Di conseguenza, in JavaScript, suggerirei di usare qualcosa di simile

array.forEach(function(i) {
    do_it(i);
});

È anche meno soggetto a errori e i browser hanno la possibilità di ottimizzare il codice.

[NOTA: non solo i browser, ma anche tu hai uno spazio per ottimizzare facilmente, basta ridefinire la forEachfunzione (il browser dipende) in modo che utilizzi i migliori trucchi più recenti! :) @AMK dice che in casi speciali vale la pena usare array.popo array.shift. Se lo fai, mettilo dietro la tenda. Il massimo è aggiungere opzioni forEachper selezionare l'algoritmo di loop.]

Inoltre, anche per linguaggi di basso livello, la migliore pratica è quella di utilizzare alcune funzioni di libreria intelligenti per operazioni complesse, se possibile.

Quelle librerie possono anche mettere le cose (multi-thread) dietro la schiena e anche programmatori specializzati le tengono aggiornate.

Ho fatto un po 'più di controllo e si scopre che in C / C ++, anche per operazioni 5e9 = (50.000x100.000), non c'è differenza tra andare su e giù se il test viene eseguito su una costante come dice @alestanis. (I risultati di JsPerf a volte sono incoerenti, ma in linea di massima dicono lo stesso: non si può fare una grande differenza.)
Quindi --isembra piuttosto una cosa "elegante". Ti fa solo sembrare un programmatore migliore. :)

D'altra parte, srotolarsi in questa situazione 5e9, mi ha portato da 12 secondi a 2,5 secondi quando sono passato di 10 secondi e a 2,1 secondi quando sono passato di 20 secondi. Era senza ottimizzazione e l'ottimizzazione ha portato le cose a un tempo poco misurabile. :) (Lo srotolamento può essere fatto nel modo sopra o usando i++, ma ciò non porta avanti le cose in JavaScript.)

Tutto sommato: mantenere i--/ i++e ++i/ i++differenze nelle interviste di lavoro, attenersi array.forEacho altre funzioni di libreria complesse quando disponibili. ;)


18
La parola chiave è "può essere". Il tuo ciclo srotolato potrebbe anche essere più lento di quello originale. Durante l'ottimizzazione, misurare sempre in modo da sapere esattamente quale impatto hanno avuto le modifiche.
jalf

1
@jalf davvero, +1. Diverse lunghezze di sgancio (> = 1) sono diverse in termini di efficienza. Ecco perché è più comodo lasciare questo lavoro alle librerie, se possibile, per non parlare del fatto che i browser funzionano su architetture diverse, quindi potrebbe essere meglio se decidono come fare array.each(...). Non credo che proverebbero a sperimentare loops di semplici loop-for.
Barney,

1
@BarnabasSzabolcs La domanda riguarda specificamente JavaScript, non C o altre lingue. In JS ci sia una differenza, si prega di vedere la mia risposta qui sotto. Sebbene non applicabile alla domanda, +1 buona risposta!
AMK

1
E il gioco è fatto: +1per procurarti un Great Answerbadge. Davvero un'ottima risposta. :)
Rohit Jain,

1
APL / A ++ non è nemmeno una lingua. Le persone li usano per esprimere che non sono interessati ai dettagli della lingua, ma qualcosa che queste lingue condividono.
Barney,

33

i-- è veloce come i++

Questo codice di seguito è veloce come il tuo, ma utilizza una variabile aggiuntiva:

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};

Il consiglio è di NON valutare la dimensione dell'array ogni volta. Per le grandi matrici si può vedere il degrado delle prestazioni.


6
Sbagli chiaramente qui. Ora il controllo del loop richiede una variabile interna aggiuntiva (ie up) e, a seconda del motore JavaScript, questo potrebbe limitare il potenziale per ottimizzare il codice. I JIT tradurranno semplici loop per dirigere i codici operativi della macchina, ma se i loop hanno troppe variabili, JIT non sarà in grado di ottimizzare altrettanto bene. Generalmente, è limitato dall'architettura o dai registri della CPU utilizzati da JIT. Inizializzare una volta e scendere semplicemente la soluzione più pulita ed è per questo che è raccomandato ovunque.
Pavel P

La variabile aggiuntiva verrà eliminata dall'ottimizzatore di un interprete / compilatore comune. Il punto è il "confronta con 0" - vedi la mia risposta per ulteriori spiegazioni.
H.-Dirk Schmitt,

1
Meglio mettere "dentro" il ciclo:for (var i=0, up=Things.length; i<up; i++) {}
entro il

22

Dal momento che sei interessato all'argomento, dai un'occhiata al post del blog di Greg Reimer su un benchmark di loop JavaScript, Qual è il modo più veloce per codificare un loop in JavaScript? :

Ho creato una suite di test di benchmarking di loop per diversi modi di codificare i loop in JavaScript. Ci sono già alcuni di questi là fuori, ma non ho trovato nessuno che abbia riconosciuto la differenza tra array nativi e raccolte HTML.

Puoi anche eseguire un test delle prestazioni su un ciclo aprendolo https://blogs.oracle.com/greimer/resource/loop-test.html(non funziona se JavaScript è bloccato nel browser, ad esempio NoScript ).

MODIFICARE:

Un benchmark più recente creato da Milan Adamovsky può essere eseguito in fase di runtime qui per diversi browser.

Per un test in Firefox 17.0 su Mac OS X 10.6 ho ottenuto il seguente ciclo:

var i, result = 0;
for (i = steps - 1; i; i--) {
  result += i;
}

come il più veloce preceduto da:

var result = 0;
for (var i = steps - 1; i >= 0; i--) {
  result += i;
}

Esempio di benchmark.


5
Quel post ha ormai 4 anni. Data la velocità (ah!) Con cui i motori JavaScript sono stati aggiornati, la maggior parte dei consigli è probabilmente obsoleta - l'articolo non menziona nemmeno Chrome ed è il motore V8 JavaScript lucido.
Yi Jiang,

Sì, mi manca quel dettaglio, grazie per averlo sottolineato. Evento in quel momento non c'era molta differenza tra --i o i ++ sul loop per.
dreamcrash,

19

Non è --o ++, è l'operazione di confronto. Con --te puoi usare un confronto con 0, mentre con ++te devi confrontarlo con la lunghezza. Sul processore, il confronto con zero è normalmente disponibile, mentre il confronto con un numero intero finito richiede una sottrazione.

a++ < length

è in realtà compilato come

a++
test (a-length)

Quindi richiede più tempo sul processore una volta compilato.


15

Risposta breve

Per il codice normale, specialmente in un linguaggio di alto livello come JavaScript , non vi sono differenze di prestazioni in i++e i--.

I criteri di prestazione sono l'uso nel forciclo e l' istruzione compare .

Questo vale per tutte le lingue di alto livello ed è per lo più indipendente dall'uso di JavaScript. La spiegazione è il codice assemblatore risultante nella riga inferiore.

Spiegazione dettagliata

Una differenza di prestazioni può verificarsi in un ciclo. Lo sfondo è che a livello di codice assembler puoi vedere che a compare with 0è solo un'istruzione che non ha bisogno di un registro aggiuntivo.

Questo confronto viene emesso ad ogni passaggio del loop e può comportare un miglioramento misurabile delle prestazioni.

for(var i = array.length; i--; )

sarà valutato con uno pseudo codice come questo:

 i=array.length
 :LOOP_START
 decrement i
 if [ i = 0 ] goto :LOOP_END
 ... BODY_CODE
 :LOOP_END

Si noti che 0 è un valore letterale o, in altre parole, un valore costante.

for(var i = 0 ; i < array.length; i++ )

sarà valutato con uno pseudo codice come questo (si suppone una normale ottimizzazione dell'interprete):

 end=array.length
 i=0
 :LOOP_START
 if [ i < end ] goto :LOOP_END
 increment i
 ... BODY_CODE
 :LOOP_END

Si noti che end è una variabile che necessita di un registro CPU . Questo può richiamare uno scambio registro aggiuntivo nel codice e ha bisogno di un più costoso dichiarazione confrontare nella ifdichiarazione.

Solo i miei 5 centesimi

Per un linguaggio di alto livello, la leggibilità, che facilita la manutenibilità, è più importante come un miglioramento delle prestazioni minori.

Normalmente la classica iterazione dall'inizio alla fine dell'array è migliore.

L'iterazione più rapida dalla fine dell'array all'inizio traduce in una sequenza inversa indesiderata.

Post scriptum

Come richiesto in un commento: la differenza --ie i--sta nella valutazione dii prima o dopo il decremento.

La migliore spiegazione è provarlo ;-) Ecco un esempio di Bash .

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9

1
1+ Buona spiegazione. Solo una domanda un po 'fuori portata, potresti per favore spiegare la differenza tra --ie i--anche?
Afshin Mehrabani,

14

Ho visto la stessa raccomandazione in Sublime Text 2.

Come già detto, il miglioramento principale non è la valutazione della lunghezza dell'array ad ogni iterazione nel ciclo for. Questa è una tecnica di ottimizzazione ben nota e particolarmente efficiente in JavaScript quando l'array fa parte del documento HTML (facendo un forper tuttoli elementi).

Per esempio,

for (var i = 0; i < document.getElementsByTagName('li').length; i++)

è molto più lento di

for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)

Da dove mi trovo, il principale miglioramento del modulo nella tua domanda è il fatto che non dichiara una variabile aggiuntiva ( lennel mio esempio)

Ma se mi chiedi, l'intero punto non riguarda l' ottimizzazione i++vs i--, ma non dover valutare la lunghezza dell'array ad ogni iterazione (puoi vedere un test benchmark su jsperf ).


1
Devo fare un'eccezione alla parola che calcola qui. Vedi il mio commento sulla risposta di Pavel . Le specifiche ECMA indicano che la lunghezza dell'array non viene calcolata quando ci si riferisce ad essa.
Kojiro,

La "valutazione" sarebbe una scelta migliore? A proposito interessante, non lo sapevo
BBog

La variabile aggiuntiva verrà eliminata dall'ottimizzatore di un interprete / compilatore comune. Lo stesso vale per la valutazione array.length. Il punto è il "confronta con 0" - vedi la mia risposta per ulteriori spiegazioni.
H.-Dirk Schmitt,

@ H.-DirkSchmitt la tua risposta di buon senso a parte, per lungo nella storia di JavaScript il compilatore non ha ottimizzato il costo delle prestazioni della catena di ricerca. AFAIK V8 è stato il primo a provare.
Kojiro,

@ H.-DirkSchmitt proprio come ha detto Kojiro, questo è un trucco ben noto e consolidato. Anche se non è più rilevante nei browser moderni, ciò non lo rende ancora obsoleto. Inoltre, farlo con l'introduzione di una nuova variabile per lunghezza o con il trucco nella domanda di OP, è ancora il modo migliore, imo. È solo una cosa intelligente da fare e una buona pratica, non penso che abbia nulla a che fare con il fatto che il compilatore è stato ottimizzato per prendersi cura di qualcosa spesso fatto in modo
errato

13

Non penso che abbia senso dire che i--è più veloce i++di JavaScript.

Prima di tutto , dipende totalmente dall'implementazione del motore JavaScript.

In secondo luogo , purché i costrutti più semplici siano JIT e tradotti in istruzioni native, quindi i++vsi-- dipenderà totalmente dalla CPU che lo esegue. Cioè, su ARM (telefoni cellulari) è più veloce scendere a 0 poiché il decremento e il confronto a zero vengono eseguiti in una singola istruzione.

Probabilmente, hai pensato che uno era più sporco dell'altro perché il modo suggerito è

for(var i = array.length; i--; )

ma il modo suggerito non è perché uno più veloce dell'altro, ma semplicemente perché se scrivi

for(var i = 0; i < array.length; i++)

quindi su ogni iterazione array.length doveva essere valutato (un motore JavaScript più intelligente forse potrebbe capire che il ciclo non cambierà la lunghezza dell'array). Anche se sembra una semplice affermazione, in realtà è una funzione che viene chiamata sotto il cofano dal motore JavaScript.

L'altro motivo, perché i--potrebbe essere considerato "più veloce", è perché il motore JavaScript deve allocare solo una variabile interna per controllare il ciclo (variabile al var i). Se hai confrontato con array.length o con qualche altra variabile, per controllare il ciclo doveva esserci più di una variabile interna e il numero di variabili interne è un bene limitato di un motore JavaScript. Meno variabili vengono utilizzate in un ciclo, maggiori sono le possibilità che JIT ha di ottimizzare. Ecco perché i--potrebbe essere considerato più veloce ...


3
Probabilmente vale la pena fare delle frasi attente su come array.lengthviene valutato. La lunghezza non viene calcolata quando ci si riferisce ad essa. (È solo una proprietà che viene impostata ogni volta che viene creato o modificato un indice di array ). Quando ci sono costi aggiuntivi, è perché il motore JS non ha ottimizzato la catena di ricerca per quel nome.
Kojiro,

Bene, non sono sicuro di ciò che dice la specifica Ecma, ma conoscere alcuni interni di diversi motori JavaScript non è semplice getLength(){return m_length; }perché ci sono delle pulizie. Ma se provi a pensare al contrario: sarebbe abbastanza ingegnoso scrivere l'implementazione dell'array in cui la lunghezza dovrebbe essere effettivamente calcolata :)
Pavel P

le specifiche ECMA richiedono che la proprietà length sia già calcolata. Il lengthdevono essere aggiornati immediatamente quando si aggiunge o cambiato una struttura a che-è-una-array-index.
Kojiro,

Quello che sto cercando di dire è che è abbastanza difficile violare le specifiche se provi a pensarci.
Pavel P,

1
x86 è come ARM in questo senso. dec/jnzvs inc eax / cmp eax, edx / jne.
Peter Cordes,

13

Poiché nessuna delle altre risposte sembra rispondere alla tua domanda specifica (più della metà mostra esempi in C e discute lingue di livello inferiore, la tua domanda è per JavaScript) ho deciso di scrivere la mia.

Quindi, eccoti qui:

Risposta semplice: i-- è generalmente più veloce perché non è necessario eseguire un confronto su 0 ogni volta che viene eseguito, i risultati dei test su vari metodi sono di seguito:

Risultati del test: come "dimostrato" da questo jsPerf, in arr.pop()realtà è di gran lunga il circuito più veloce. Ma, concentrandosi su --i, i--, i++e ++icome lei ha chiesto nella sua interrogazione, qui ci sono jsPerf (sono da più di jsPerf, si prega di consultare le fonti di seguito) risultati riassunti:

--ie i--sono gli stessi in Firefox mentrei-- è più veloce in Chrome.

In Chrome un ciclo base per ( for (var i = 0; i < arr.length; i++)) è più veloce di i--e--i mentre in Firefox è più lento.

Sia in Chrome che in Firefox un cache arr.length è significativamente più veloce con Chrome in avanti di circa 170.000 operazioni al secondo.

Senza una differenza significativa, ++iè più veloce che i++nella maggior parte dei browser, AFAIK, non è mai il contrario in nessun browser.

Riassunto più breve: arr.pop() è di gran lunga il ciclo più veloce; per i loop specificamente menzionati, i--è il loop più veloce.

Fonti: http://jsperf.com/fastest-array-loops-in-javascript/15 , http://jsperf.com/ipp-vs-ppi-2

Spero che questo risponda alla tua domanda.


1
Sembra che il tuo poptest sembri così veloce perché sta riducendo la dimensione dell'array a 0 per la maggior parte del loop, per quanto ne so. Tuttavia, per essere sicuro che le cose siano giuste, ho progettato questo jsperf per creare l'array allo stesso modo con ogni test - che sembra mostrare .shift()il vincitore per i miei pochi browser - non quello che mi sarei aspettato però :) jsperf.com / compare-different-types-of-looping
Pebbl

Votato per essere l'unico a menzionare ++i: D
jaggedsoft il

12

Dipende dal posizionamento dell'array in memoria e dal rapporto di hit delle pagine di memoria mentre si accede a quell'array.

In alcuni casi, l'accesso ai membri dell'array nell'ordine delle colonne è più rapido dell'ordine delle righe a causa dell'aumento del rapporto di risultati.


1
Se solo l'OP avesse chiesto perché attraversare la stessa matrice in ordini diversi può richiedere tempi diversi ..
dcow

1
Considerando che nei sistemi operativi la loro gestione della memoria si basa sul paging, quando un processo necessita di dati che non si trovano nelle pagine memorizzate nella cache, si verifica un errore di pagina nel sistema operativo e deve portare la pagina di destinazione nella cache della CPU e sostituirla con un'altra pagina, pertanto, causa un sovraccarico nell'elaborazione che richiede più tempo rispetto a quando la pagina di destinazione si trova nella cache della CPU. Supponiamo di aver definito un array di grandi dimensioni che ogni riga in esso è più grande della dimensione del sistema operativo della pagina e di accedervi in ​​ordine di riga, in questo caso il rapporto di errore della pagina aumenta e il risultato è più lento dell'accesso all'ordine di colonna di quell'array.
Akbar Bayati,

Gli errori di pagina non sono la stessa cosa dei mancati cache. Si verifica un errore di pagina solo se la memoria è stata paginata su disco. L'accesso alle matrici multidimensionali in ordine sequenziale di come sono archiviate in memoria è più rapido a causa della localizzazione della cache (utilizzando tutti i byte di una riga della cache quando viene recuperato), non a causa di errori di pagina. (a meno che il tuo set di lavoro non sia troppo grande per il computer che stai utilizzando.)
Peter Cordes,

10

L'ultima volta che mi sono preoccupato è stato quando ho scritto 6502 assembly (8-bit, sì!). Il grande vantaggio è che la maggior parte delle operazioni aritmetiche (in particolare i decrementi) ha aggiornato una serie di bandiere, una delle quali eraZ l'indicatore "raggiunto zero".

Quindi, alla fine del ciclo hai appena fatto due istruzioni: DEC(decremento) e JNZ(salta se non zero), non è necessario alcun confronto!


Nel caso di JavaScript ovviamente non si applica (poiché funziona su CPU che non hanno tali codici op). Molto probabilmente il vero motivo dietro il i--vs i++è che con il primo non si introducono variabili di controllo extra nell'ambito del loop. Vedi la mia risposta qui sotto ...
Pavel P

1
giusto, non si applica davvero; ma è uno stile C molto comune e sembra più pulito a quelli di noi che si sono abituati. :-)
Javier,

x86 e ARM sono entrambi come 6502 in questo senso. dec/jnzinvece che inc/cmp/jneper il caso x86. Non vedrai un ciclo vuoto correre più veloce (entrambi satureranno il rendimento del ramo), ma il conto alla rovescia riduce leggermente il sovraccarico del ciclo. Gli attuali prefetcher Intel HW non sono infastiditi dai modelli di accesso alla memoria che vanno in ordine decrescente. Credo che le CPU più vecchie potessero tracciare come 4 stream all'indietro e 6 o 10 stream in avanti, IIRC.
Peter Cordes,

7

Può essere spiegato da JavaScript (e da tutte le lingue) alla fine trasformato in codici operativi per l'esecuzione sulla CPU. Le CPU hanno sempre una singola istruzione per il confronto con zero, che è dannatamente veloce.

A parte questo, se puoi garantire che countè sempre >= 0, potresti semplificare a:

for (var i = count; i--;)
{
  // whatever
}

1
Un codice sorgente più breve non significa necessariamente che sarà più veloce. L'hai misurato?
Greg Hewgill,

Ooops, ho perso questo. Chiodo sulla testa lì cap.
Robert,

1
Mi piacerebbe vedere le origini dell'assembly in cui il confronto con 0 è diverso. Sono passati alcuni anni, ma una volta ho fatto un sacco di codifica di assemblaggio e non riesco per la vita a me pensare a un modo per fare un confronto / test contro 0 in un modo che non può essere altrettanto veloce per qualsiasi altro numero intero. Tuttavia, quello che dici suona vero. Frustrato dal fatto che non riesco a capire perché!
Brian Knoblauch,

7
@Brian Knoblauch: Se usi un'istruzione come "dec eax" (codice x86), quell'istruzione imposta automaticamente il flag Z (zero) che puoi testare immediatamente senza dover usare un'altra istruzione di confronto tra.
Greg Hewgill,

1
Durante l'interpretazione di Javascript, dubito che gli opcode saranno il collo di bottiglia. È più probabile che meno token significhi che l'interprete può elaborare il codice sorgente più velocemente.
Todd Owen,

6

Questo non dipende dal --o++ segno , ma dipende dalle condizioni applicate nel ciclo.

Ad esempio: il tuo ciclo è più veloce se la variabile ha un valore statico che se il tuo ciclo verifica sempre le condizioni, come la lunghezza di un array o altre condizioni.

Ma non preoccuparti di questa ottimizzazione, perché questa volta il suo effetto è misurato in nanosecondi.


più cicli di nanosecondi possono diventare secondi ... Non è mai una cattiva idea ottimizzare quando hai tempo per farlo
Buksy

6

for(var i = array.length; i--; )non è molto più veloce. Ma quando si sostituisce array.lengthcon super_puper_function(), potrebbe essere significativamente più veloce (poiché viene chiamato in ogni iterazione). Questa è la differenza

Se hai intenzione di cambiarlo nel 2014, non devi pensare all'ottimizzazione. Se lo cambierai con "Cerca e sostituisci", non devi pensare all'ottimizzazione. Se non hai tempo, non devi pensare all'ottimizzazione. Ma ora, hai tempo di pensarci.

PS: i--non è più veloce di i++.


6

Per farla breve: non c'è assolutamente alcuna differenza nel farlo in JavaScript.

Prima di tutto, puoi provarlo tu stesso:

Non solo puoi testare ed eseguire qualsiasi script in qualsiasi libreria JavaScript, ma hai anche accesso a tutto il gruppo di script precedentemente scritti, oltre alla possibilità di vedere le differenze tra i tempi di esecuzione in diversi browser su piattaforme diverse.

Per quanto puoi vedere, non c'è differenza tra le prestazioni in qualsiasi ambiente.

Se vuoi migliorare le prestazioni del tuo script, cose che puoi provare a fare:

  1. Avere un var a = array.length; dichiarazione in modo che non si calcolerà il suo valore ogni volta nel ciclo
  2. Svolgi il ciclo srotolando http://en.wikipedia.org/wiki/Loop_unwinding

Ma devi capire che il miglioramento che puoi ottenere sarà così insignificante, che per lo più non dovresti nemmeno preoccupartene.

La mia opinione personale sul perché sia ​​apparso un tale malinteso (Dec vs Inc)

Molto, molto tempo fa, c'era un'istruzione macchina comune, DSZ (Decrement and Skip on Zero). Le persone che hanno programmato in linguaggio assembly hanno usato queste istruzioni per implementare loop per salvare un registro. Ora questi fatti antichi sono obsoleti e sono abbastanza sicuro che non otterrai alcun miglioramento delle prestazioni in nessuna lingua usando questo pseudo miglioramento.

Penso che l'unico modo in cui tale conoscenza possa propagarsi nel nostro tempo sia quando leggi il codice di un'altra persona. Guarda una simile costruzione e chiedi perché è stata implementata e qui la risposta: "migliora le prestazioni perché si confronta con zero". Ti sei lasciato sconcertato dalle conoscenze più elevate del tuo collega e pensi di usarlo per essere molto più intelligente :-)


Interessante, ma per i tuoi test eseguiti su Firefox 16.0.2 su win7, il ciclo di decremento è stato del 29% più lento ... EDIT: Ignoralo. Test ripetuti si sono rivelati inconcludenti, c'è una quantità sorprendente di rumore nei miei risultati dei test per le esecuzioni successive. Non sono del tutto sicuro del perché.

Sì, ho provato a spiegarlo chiudendo tutto il resto ed eseguendo i test. Ancora ottenuto risultati traballanti. Molto strano.

Penso che tu abbia perso il vero punto per cui andare a zero è considerato migliore in JavaScript. È principalmente perché in questo modo solo una variabile controlla l'esecuzione del loop, ad esempio in questo modo l'ottimizzatore / JITer ha più margini di miglioramento. L'uso array.lengthnon comporta necessariamente una penalizzazione delle prestazioni, semplicemente perché la macchina virtuale JS è abbastanza intelligente da capire se l'array non è modificato dal corpo del loop. Vedi la mia risposta qui sotto.
Pavel P

1
nitpicking: questo fatto antico (ottimizzazioni del linguaggio assembly) non è obsoleto, ma arcano. come in te non devi sapere se non lo fai davvero. :-)
Javier

6

Ho fatto un confronto su jsbench .

Come ha sottolineato Alestani, una cosa che richiede tempo in loop ascendenti, è valutare, per ogni iterazione, la dimensione della matrice. In questo ciclo:

for ( var i = 1; i <= array.length; i++ )

si valuta .lengthogni volta che si incrementa i. In questo:

for ( var i = 1, l = array.length; i <= l; i++ )

valuti .lengthsolo una volta, quando dichiari i. In questo:

for ( var i = array.length; i--; )

il confronto è implicito, succede poco prima del decremento ie il codice è molto leggibile. Tuttavia, ciò che può fare una differenza eccezionale, è ciò che metti nel loop.

Ciclo con chiamata alla funzione (definita altrove):

for (i = values.length; i-- ;) {
  add( values[i] );
}

Ciclo con codice interno:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}

Se puoi incorporare il tuo codice, invece di chiamare una funzione, senza sacrificare la leggibilità, puoi avere un ciclo di un ordine di grandezza più veloce!


Nota : poiché il browser sta diventando bravo a integrare funzioni semplici, dipende davvero dalla complessità del codice. Quindi, profilo prima di ottimizzare, perché

  1. Il collo di bottiglia potrebbe essere altrove (ajax, reflow, ...)
  2. Puoi scegliere un algoritmo migliore
  3. Puoi scegliere una migliore struttura di dati

Ma ricorda:

Il codice è scritto per essere letto dalle persone e solo per inciso da eseguire sulle macchine.


+1 per questa risposta e per il benchmark. Ho aggiunto per ogni test e rielaborato il benchmark in un file autonomo eseguibile nel browser e in Nodo. jsfiddle.net/hta69may/2 . Per il nodo "Reverse loop, confronto implicito, codice incorporato" è il più veloce. Ma i test in FF 50 hanno mostrato risultati curiosi: non solo i tempi erano quasi 10 volte inferiori (!) Ma entrambi i test "forEach" erano veloci quanto "reverse loop". Forse i ragazzi di Node dovrebbero usare il motore JS di Mozilla invece di V8? :)
Fr0sT

4

Il modo in cui lo stai facendo ora non è più veloce (a parte il fatto che si tratta di un ciclo indefinito, immagino che tu intendessi farlo i--.

Se vuoi renderlo più veloce fai:

for (i = 10; i--;) {
    //super fast loop
}

ovviamente non lo noteresti su un ciclo così piccolo. Il motivo per cui è più veloce è perché stai decrementando i mentre controlli che sia "vero" (diventa "falso" quando raggiunge lo 0)


1
Ti manca un punto e virgola? (i = 10; i--;)
searlea,

Sì sì, ho corretto l'errore. E se saremo pignoli, ti faccio notare che hai dimenticato il tuo punto e virgola dopo il tuo ... Eh.
DJD87,

Perché sarebbe più veloce? Una fonte più breve non significa che sarà più veloce. L'hai misurato?
Greg Hewgill,

4
Sì, l'ho misurato e non sto dicendo che una fonte più breve lo rende più veloce, ma un minor numero di operazioni lo rende più veloce.
peirix,

4

A volte apportare alcune modifiche molto minori al modo in cui scriviamo il nostro codice può fare una grande differenza per la velocità con cui il nostro codice viene effettivamente eseguito. Un'area in cui una piccola modifica del codice può fare una grande differenza nei tempi di esecuzione è quella in cui abbiamo un ciclo for che sta elaborando un array. Laddove l'array è costituito da elementi sulla pagina Web (come i pulsanti di opzione), la modifica ha l'effetto maggiore ma vale comunque la pena applicare questa modifica anche quando l'array è interno al codice Javascript.

Il modo convenzionale di codificare un ciclo for per elaborare un array è come questo:

for (var i = 0; i < myArray.length; i++) {...

Il problema è che valutare la lunghezza dell'array usando myArray.length richiede tempo e il modo in cui abbiamo codificato il ciclo significa che questa valutazione deve essere eseguita ogni volta intorno al ciclo. Se l'array contiene 1000 elementi, la lunghezza dell'array verrà valutata 1001 volte. Se stessimo osservando i pulsanti di opzione e avessi myForm.myButtons.length, ci vorrà ancora più tempo per valutare poiché il gruppo appropriato di pulsanti all'interno del modulo specificato deve essere individuato prima che la lunghezza possa essere valutata ogni volta intorno al ciclo.

Ovviamente non ci aspettiamo che la lunghezza dell'array cambi mentre la stiamo elaborando, quindi tutti questi ricalcoli della lunghezza si aggiungono inutilmente al tempo di elaborazione. (Naturalmente se all'interno del ciclo è presente un codice che aggiunge o rimuove le voci di array, la dimensione dell'array può cambiare tra le iterazioni e quindi non possiamo modificare il codice che lo verifica)

Quello che possiamo fare per correggere questo per un loop in cui la dimensione è fissa è valutare la lunghezza una volta all'inizio del loop e salvarlo in una variabile. Possiamo quindi testare la variabile per decidere quando terminare il ciclo. Ciò è molto più rapido rispetto alla valutazione della lunghezza dell'array ogni volta, in particolare quando l'array contiene più di poche voci o fa parte della pagina Web.

Il codice per fare questo è:

for (var i = 0, var j = myArray.length; i < j; i++) {...

Quindi ora valutiamo la dimensione dell'array solo una volta e testiamo il nostro contatore di loop rispetto alla variabile che contiene quel valore ogni volta intorno al loop. È possibile accedere a questa variabile extra molto più velocemente rispetto alla valutazione della dimensione dell'array e quindi il nostro codice verrà eseguito molto più velocemente di prima. Abbiamo solo una variabile in più nel nostro script.

Spesso non importa in quale ordine elaboriamo l'array fintanto che tutte le voci dell'array vengono elaborate. In questo caso, possiamo rendere il nostro codice leggermente più veloce eliminando la variabile aggiuntiva che abbiamo appena aggiunto ed elaborando l'array in ordine inverso.

Il codice finale che elabora il nostro array nel modo più efficiente possibile è:

for (var i = myArray.length-1; i > -1; i--) {...

Questo codice valuta ancora la dimensione dell'array solo una volta all'inizio, ma invece di confrontare il contatore di loop con una variabile lo confrontiamo con una costante. Poiché una costante è ancora più efficace di accesso rispetto a una variabile e poiché disponiamo di un numero di istruzioni di assegnazione inferiore rispetto a prima, la nostra terza versione del codice è ora leggermente più efficiente della seconda versione e notevolmente più efficiente della prima.


4

++vs. --non importa perché JavaScript è un linguaggio interpretato, non un linguaggio compilato. Ogni istruzione si traduce in più di un linguaggio macchina e non dovresti preoccuparti dei dettagli cruenti.

Le persone che stanno parlando dell'uso --(o ++) per un uso efficiente delle istruzioni di assemblaggio hanno torto. Queste istruzioni si applicano all'aritmetica dei numeri interi e non ci sono numeri interi in JavaScript, solo numeri .

Dovresti scrivere un codice leggibile.


3

In molti casi, ciò non ha sostanzialmente nulla a che fare con il fatto che i processori possono confrontare a zero più velocemente di altri confronti.

Questo perché solo pochi motori Javascript (quelli nell'elenco JIT) generano effettivamente il codice del linguaggio macchina.

La maggior parte dei motori Javascript crea una rappresentazione interna del codice sorgente che poi interpretano (per avere un'idea di come sia, dai un'occhiata in fondo a questa pagina su SpiderMonkey di Firefox ). Generalmente se un pezzo di codice fa praticamente la stessa cosa ma porta a una rappresentazione interna più semplice, verrà eseguito più velocemente.

Tieni presente che con compiti semplici come l'aggiunta / sottrazione di uno da una variabile o il confronto di una variabile con qualcosa, il sovraccarico dell'interprete che passa da una "istruzione" interna alla successiva è piuttosto elevato, quindi meno "istruzioni" sono utilizzato internamente dal motore JS, meglio è.


3

Bene, non conosco JavaScript, dovrebbe davvero essere solo una questione di lunghezza dell'array di rivalutazione e forse qualcosa a che fare con gli array associativi (se decrementi, è improbabile che debbano essere allocate nuove voci - se l'array è denso, cioè qualcuno può ottimizzare per quello).

Nell'assemblaggio di basso livello, c'è un'istruzione di loop, chiamata DJNZ (decrementa e salta se diversa da zero). Quindi il decremento e il salto sono tutti in un'unica istruzione, rendendolo forse sempre leggermente più veloce di INC e JL / JB (incremento, salta se inferiore a / salta se sotto). Inoltre, il confronto con zero è più semplice rispetto al confronto con un altro numero. Ma tutto ciò è davvero marginale e dipende anche dall'architettura del bersaglio (potrebbe fare la differenza, ad esempio, su Arm in uno smartphone).

Non mi aspetto che queste differenze di basso livello abbiano un impatto così grande sui linguaggi interpretati, non ho visto DJNZ tra le risposte, quindi ho pensato di condividere un pensiero interessante.


Per la cronaca, DJNZè un'istruzione nell'ISA 8051 (z80). x86 ha dec/jnzinvece inc/cmp/jne, e apparentemente arm ha qualcosa di simile. Sono abbastanza sicuro che questa non sia la causa della differenza di Javascript; questo è avere altro da valutare nella condizione di loop.
Peter Cordes,

3

E ' usato per dire che --I era più veloce (in C ++) perché non v'è un solo risultato, il valore decrementato. i-- deve memorizzare il valore decrementato in i e conservare anche il valore originale come risultato (j = i--;). Nella maggior parte dei compilatori questo utilizzava due registri anziché uno che poteva causare la scrittura in memoria di un'altra variabile anziché la sua conservazione come variabile di registro.

Concordo con gli altri che hanno detto che in questi giorni non fa differenza.


3

In parole molto semplici

"i-- e i ++. In realtà, entrambi richiedono lo stesso tempo".

ma in questo caso quando si ha un'operazione incrementale .. il processore valuta la .length ogni volta che la variabile viene incrementata di 1 e in caso di decremento .. in particolare in questo caso, valuterà .length solo una volta finché non si ottiene 0.


3

Primo, i++ei-- prendere esattamente lo stesso tempo su qualsiasi linguaggio di programmazione, tra cui JavaScript.

Il codice seguente richiede tempi molto diversi.

Veloce:

for (var i = 0, len = Things.length - 1; i <= len; i++) { Things[i] };

Lento:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

Pertanto anche il codice seguente richiede tempi diversi.

Veloce:

for (var i = Things.length - 1; i >= 0; i--) { Things[i] };

Lento:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

PS Slow è lento solo per poche lingue (motori JavaScript) a causa dell'ottimizzazione del compilatore. Il modo migliore è usare '<' invece '<=' (o '=') e '--i' invece 'i--' .


3

I-- o i ++ non consumano molto tempo. Se vai in profondità nell'architettura della CPU, ++è più veloce di quello --, poiché l' --operazione farà il complemento a 2, ma accadrà all'interno dell'hardware, quindi questo lo renderà veloce e nessuna differenza sostanziale tra ++e --anche queste operazioni sono considerate del minor tempo impiegato nella CPU.

Il ciclo for funziona in questo modo:

  • Inizializza la variabile una volta all'inizio.
  • Controllare il vincolo nel secondo operando del ciclo, <, >, <=, etc.
  • Quindi applicare il ciclo.
  • Incrementa il loop e loop di nuovo lancia questi processi di nuovo.

Così,

for (var i = Things.length - 1; i >= 0; i--) {
    Things[i]
}; 

calcolerà la lunghezza dell'array solo una volta all'inizio e questo non è molto tempo, ma

for(var i = array.length; i--; ) 

calcolerà la lunghezza di ogni ciclo, quindi consumerà molto tempo.


var i = Things.length - 1; i >= 0; i--calcolerà anche la lunghezza 1 volta.
Dmitry,

1
Non sono sicuro di cosa significhi " --operazione farà il complemento a 2", ma suppongo che significhi che negherà qualcosa. No, non nega nulla su nessuna architettura. Sottraendo 1 è semplice come aggiungere 1, basta creare un circuito che prende in prestito piuttosto che trasportare.
Olathe,

1

L'approccio migliore per rispondere a questo tipo di domanda è effettivamente provarlo. Crea un ciclo che conti un milione di iterazioni o altro e fallo in entrambi i modi. Cronometra entrambi i loop e confronta i risultati.

La risposta dipenderà probabilmente dal browser che stai utilizzando. Alcuni avranno risultati diversi rispetto ad altri.


4
Ciò non risponderebbe ancora alla sua domanda sul perché sia più veloce.
Otterrebbe

3
È vero. Tuttavia, senza conoscere l'esatta implementazione di ciascun motore Javascript in ciascun browser, è quasi impossibile rispondere alla parte "why". Molte delle risposte qui si basano su raccomandazioni aneddotiche come "usa predecrement anziché postdecrement" (un trucco di ottimizzazione C ++) e "confronta contro zero" che potrebbe essere vero nei linguaggi compilati con codice nativo ma Javascript è piuttosto lontano dal metallo nudo del PROCESSORE.
Greg Hewgill,

4
-1 Non sono completamente d'accordo sul fatto che l'approccio migliore sia provarlo. Un paio di esempi non sostituiscono la conoscenza collettiva, e questo è il punto centrale di forum come questo.
Christophe

1

Lo adoro, molti mark up ma nessuna risposta: D

In poche parole, un confronto con zero è sempre il confronto più veloce

Quindi (a == 0) è in realtà più veloce nel restituire True di (a == 5)

È piccolo e insignificante e con 100 milioni di righe in una raccolta è misurabile.

vale a dire su un loop up potresti dire dove i <= array.length e aumentare i

in un ciclo discendente potresti dire dove i> = 0 e invece decrementare i.

Il confronto è più veloce. Non la "direzione" del loop.


Non c'è risposta alla domanda come indicato perché i motori Javascript sono tutti diversi e la risposta dipende esattamente dal browser su cui la stai misurando.
Greg Hewgill,

No, è sostanzialmente accettato il confronto con lo zero è il più veloce. Sebbene anche le tue affermazioni siano corrette, la regola d'oro del confronto con zero è assoluta.
Robert,

4
Questo è vero solo se il compilatore sceglie di effettuare tale ottimizzazione, il che non è certo garantito. Il compilatore potrebbe generare esattamente lo stesso codice per (a == 0) e (a == 5), ad eccezione del valore della costante. Una CPU non comparerà con 0 più rapidamente che con qualsiasi altro valore, se entrambi i lati del confronto sono variabili (dal punto di vista della CPU). Generalmente solo i compilatori di codice nativo hanno l'opportunità di ottimizzare a questo livello.
Greg Hewgill,

1

AIUTA ALTRI A EVITARE UNA TESTA --- VOTA QUESTO !!!

La risposta più popolare in questa pagina non funziona per Firefox 14 e non passa jsLinter. I cicli "while" richiedono un operatore di confronto, non un compito. Funziona su Chrome, Safari e persino ie. Ma muore in Firefox.

QUESTO È ROTTO!

var i = arr.length; //or 10
while(i--)
{
  //...
}

QUESTO FUNZIONA! (funziona su Firefox, passa jsLinter)

var i = arr.length; //or 10
while(i>-1)
{
  //...
  i = i - 1;
}

2
L'ho appena provato su Firefox 14 e ha funzionato bene. Ho visto esempi di librerie di produzione che usano while (i--)e che funzionano su molti browser. Potrebbe esserci stato qualcosa di strano nel tuo test? Stavi usando una versione beta di Firefox 14?
Matt Browne,

1

Questa è solo un'ipotesi, ma forse è perché è più facile per il processore confrontare qualcosa con 0 (i> = 0) invece che con un altro valore (i <Things.length).


3
+1 Non altri hanno votato verso il basso. Sebbene la ripetuta valutazione di .length sia molto più un problema dell'incremento / decremento effettivo, il controllo per la fine del loop potrebbe essere importante. Il mio compilatore C fornisce alcuni avvertimenti su loop come: "osservazione # 1544-D: (ULP 13.1) Rilevato il conteggio dei loop. Consigliare il conteggio dei loop poiché il rilevamento degli zeri è più semplice"
harper
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.