Esiste una differenza di prestazioni tra i++
e ++i
se il valore risultante non viene utilizzato?
Esiste una differenza di prestazioni tra i++
e ++i
se il valore risultante non viene utilizzato?
Risposte:
Riepilogo esecutivo: No.
i++
potrebbe potenzialmente essere più lento di ++i
, poiché il vecchio valore di i
potrebbe aver bisogno di essere salvato per un uso successivo, ma in pratica tutti i compilatori moderni lo ottimizzeranno via.
Possiamo dimostrarlo guardando il codice per questa funzione, sia con ++i
che i++
.
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
I file sono gli stessi, ad eccezione di ++i
e i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
Li compileremo e otterremo anche l'assemblatore generato:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
E possiamo vedere che sia l'oggetto generato che i file assembler sono gli stessi.
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
posto di i++
. Non c'è assolutamente alcun motivo per non farlo, e se il tuo software passa mai attraverso una toolchain che non lo ottimizza, il tuo software sarà più efficiente. Considerando che è tanto facile da scrivere ++i
quanto da digitare i++
, in realtà non ci sono scuse per non usarlo ++i
in primo luogo.
Da Efficienza contro l'intenzione da Andrew Koenig:
Innanzitutto, è tutt'altro che ovvio che
++i
è più efficiente dii++
, almeno per quanto riguarda le variabili intere.
E :
Quindi la domanda che ci si dovrebbe porre non è quale di queste due operazioni sia più veloce, è quale di queste due operazioni esprime più accuratamente ciò che si sta tentando di realizzare. Sottolineo che se non si utilizza il valore dell'espressione, non c'è mai un motivo da usare
i++
invece di++i
, perché non c'è mai un motivo per copiare il valore di una variabile, incrementare la variabile e quindi buttare via la copia.
Quindi, se il valore risultante non viene utilizzato, lo userei ++i
. Ma non perché sia più efficiente: perché afferma correttamente il mio intento.
i++
nello stesso modo in cui codifico i += n
oi = i + n
, cioè, sotto forma di destinazione verb oggetto , con l' obiettivo di operando a sinistra del verbo dell'operatore. Nel caso di i++
, non esiste un oggetto giusto , ma la regola si applica comunque, mantenendo l' obiettivo a sinistra dell'operatore verbo .
Una risposta migliore è che ++i
a volte sarà più veloce ma mai più lenta.
Tutti sembrano presumere che si i
tratti di un normale tipo incorporato come int
. In questo caso non ci saranno differenze misurabili.
Tuttavia, se i
è di tipo complesso, potresti trovare una differenza misurabile. Perché i++
devi fare una copia della tua classe prima di incrementarla. A seconda di ciò che è coinvolto in una copia, potrebbe effettivamente essere più lento poiché con ++it
te puoi semplicemente restituire il valore finale.
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
Un'altra differenza è che con ++i
te hai la possibilità di restituire un riferimento anziché un valore. Ancora una volta, a seconda di ciò che è coinvolto nella creazione di una copia del tuo oggetto, questo potrebbe essere più lento.
Un esempio del mondo reale di dove ciò può accadere sarebbe l'uso di iteratori. È improbabile che la copia di un iteratore sia un collo di bottiglia nella tua applicazione, ma è comunque buona prassi prendere l'abitudine di utilizzare ++i
invece di i++
dove il risultato non è influenzato.
Risposta breve:
Non c'è mai alcuna differenza tra i++
e ++i
in termini di velocità. Un buon compilatore non dovrebbe generare codice diverso nei due casi.
Risposta lunga:
Ciò che ogni altra risposta non menziona è che la differenza tra ++i
versus i++
ha senso solo nell'espressione che trova.
In caso di for(i=0; i<n; i++)
, the i++
è solo nella sua stessa espressione: c'è un punto di sequenza prima di i++
e c'è uno dopo di esso. Pertanto, l'unico codice macchina generato è "aumenta i
di 1
" ed è ben definito il modo in cui questo è sequenziato rispetto al resto del programma. Quindi, se lo cambiassi in prefisso ++
, non importerebbe minimamente, otterresti comunque il codice della macchina "aumenta i
di 1
".
Le differenze tra ++i
e i++
contano solo in espressioni come array[i++] = x;
contro array[++i] = x;
. Alcuni potrebbero obiettare e dire che il postfix sarà più lento in tali operazioni perché il registro in cui i
risiede deve essere ricaricato in seguito. Ma poi nota che il compilatore è libero di ordinare le tue istruzioni nel modo che preferisce, purché non "rompa il comportamento della macchina astratta" come lo chiama lo standard C.
Quindi, mentre puoi supporre che array[i++] = x;
venga tradotto in codice macchina come:
i
nel registro A.i
nel registro A // inefficiente perché istruzioni aggiuntive qui, l'abbiamo già fatto una volta.i
.il compilatore potrebbe anche produrre il codice in modo più efficiente, come ad esempio:
i
nel registro A.i
.Solo perché tu come programmatore C sei addestrato a pensare che il postfix ++
avvenga alla fine, il codice macchina non deve essere ordinato in quel modo.
Quindi non c'è differenza tra prefisso e postfisso ++
in C. Ora, ciò che si dovrebbe variare da programmatore C è che le persone che usano in modo incoerente il prefisso in alcuni casi e il postfisso in altri casi, senza alcun motivo logico. Ciò suggerisce che non sono sicuri su come funzioni la C o che abbiano una conoscenza errata della lingua. Questo è sempre un brutto segno, a sua volta suggerisce che stanno prendendo altre decisioni discutibili nel loro programma, basate su superstizioni o "dogmi religiosi".
"Il prefisso ++
è sempre più veloce" è in effetti uno di questi falsi dogmi che è comune tra gli aspiranti programmatori C.
Prendendo una foglia da Scott Meyers, Più efficace c ++ Voce 6: Distinguere tra prefisso e postfisso forme di operazioni di incremento e decremento .
La versione del prefisso è sempre preferita rispetto al postfisso per quanto riguarda gli oggetti, specialmente per quanto riguarda gli iteratori.
Il motivo di ciò se si osserva il modello di chiamata degli operatori.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
Guardando questo esempio è facile vedere come l'operatore prefisso sarà sempre più efficiente del postfisso. A causa della necessità di un oggetto temporaneo nell'uso del postfix.
Questo è il motivo per cui quando vedi esempi che usano iteratori usano sempre la versione del prefisso.
Ma come fai notare per int non c'è effettivamente alcuna differenza a causa dell'ottimizzazione del compilatore che può avere luogo.
Ecco un'ulteriore osservazione se sei preoccupato per la micro ottimizzazione. I cicli decrescenti possono "possibilmente" essere più efficienti dei circuiti incrementali (a seconda dell'architettura del set di istruzioni, ad esempio ARM), dato che:
for (i = 0; i < 100; i++)
Su ogni ciclo avrai un'istruzione ciascuno per:
1
a i
. i
è inferiore a 100
.i
è inferiore a 100
.Considerando che un ciclo in decremento:
for (i = 100; i != 0; i--)
Il loop avrà un'istruzione per ciascuno di:
i
, impostando il flag di stato del registro CPU.Z==0
).Ovviamente questo funziona solo quando si decrementa a zero!
Ricordato dalla Guida per gli sviluppatori del sistema ARM.
Si prega di non lasciare che la questione di "quale sia più veloce" sia il fattore decisivo di quale utilizzare. È probabile che non ti interesserai mai così tanto, e inoltre, il tempo di lettura del programmatore è molto più costoso del tempo della macchina.
Usa quello che ha più senso per l'essere umano che legge il codice.
Prima di tutto: la differenza tra i++
ed ++i
è trascurabile in C.
Ai dettagli.
++i
è più veloceIn C ++, ++i
è più efficiente iff i
è una sorta di oggetto con un operatore di incremento sovraccarico.
Perché?
In ++i
, l'oggetto viene prima incrementato e successivamente può essere passato come riferimento const a qualsiasi altra funzione. Questo non è possibile se l'espressione è foo(i++)
perché ora è necessario eseguire l'incremento prima di foo()
chiamare, ma è necessario passare il vecchio valore foo()
. Di conseguenza, il compilatore è costretto a fare una copia i
prima di eseguire l'operatore di incremento sull'originale. Le chiamate aggiuntive di costruttore / distruttore sono la parte negativa.
Come notato sopra, questo non si applica ai tipi fondamentali.
i++
può essere più veloceSe non è necessario chiamare alcun costruttore / distruttore, come sempre in C, ++i
e i++
dovrebbe essere altrettanto veloce, giusto? No. Sono praticamente ugualmente veloci, ma potrebbero esserci piccole differenze, che la maggior parte degli altri risponditori ha sbagliato.
Come può i++
essere più veloce?
Il punto sono le dipendenze dei dati. Se il valore deve essere caricato dalla memoria, due operazioni successive devono essere eseguite con esso, incrementandolo e utilizzandolo. Con ++i
, è necessario eseguire l'incremento prima di poter utilizzare il valore. Con i++
, l'uso non dipende dall'incremento e la CPU può eseguire l'operazione d'uso parallelamente all'operazione di incremento. La differenza è al massimo un ciclo della CPU, quindi è davvero trascurabile, ma è lì. Ed è il contrario, quindi molti si aspetterebbero.
++i
o i++
viene utilizzato all'interno di un'altra espressione, la modifica tra di esse modifica la semantica dell'espressione, quindi ogni possibile guadagno / perdita di prestazione è fuori discussione. Se sono autonomi, ovvero il risultato dell'operazione non viene utilizzato immediatamente, quindi qualsiasi compilatore decente lo compilerebbe nella stessa cosa, ad esempio un'istruzione di INC
assemblaggio.
i++
e ++i
possono essere utilizzate in modo intercambiabile in quasi tutte le situazioni possibili regolando le costanti del loop di una, quindi sono quasi equivalenti in ciò che fanno per il programmatore. 2) Anche se entrambi vengono compilati con la stessa istruzione, la loro esecuzione differisce per la CPU. Nel caso di i++
, la CPU può calcolare l'incremento in parallelo ad alcune altre istruzioni che usano lo stesso valore (le CPU lo fanno davvero!), Mentre con ++i
la CPU deve programmare le altre istruzioni dopo l'incremento.
if(++foo == 7) bar();
e if(foo++ == 6) bar();
sono funzionalmente equivalenti. Tuttavia, il secondo può essere più veloce di un ciclo, poiché il confronto e l'incremento possono essere calcolati in parallelo dalla CPU. Non che questo singolo ciclo sia molto importante, ma la differenza è lì.
<
per esempio contro <=
) dove ++
vengono di solito utilizzate, quindi la conversione tra i sebbene è spesso facilmente possibile.
@Mark Anche se al compilatore è permesso ottimizzare la copia temporanea (basata sullo stack) della variabile e gcc (nelle ultime versioni) lo fa, non significa che tutti i compilatori lo faranno sempre.
L'ho appena testato con i compilatori che utilizziamo nel nostro progetto attuale e 3 su 4 non lo ottimizzano.
Non dare mai per scontato che il compilatore funzioni correttamente, specialmente se il codice forse più veloce, ma mai più lento, è facile da leggere.
Se non hai un'implementazione davvero stupida di uno degli operatori nel tuo codice:
Preferisco sempre ++ i rispetto a i ++.
In C, il compilatore può generalmente ottimizzarli affinché siano gli stessi se il risultato non viene utilizzato.
Tuttavia, in C ++ se si utilizzano altri tipi che forniscono i propri operatori ++, è probabile che la versione del prefisso sia più veloce della versione postfix. Quindi, se non hai bisogno della semantica postfix, è meglio usare l'operatore prefisso.
Mi viene in mente una situazione in cui Postfix è più lento dell'incremento del prefisso:
Immagina un processore con registro A
sia usato come accumulatore ed è l'unico registro usato in molte istruzioni (alcuni piccoli microcontrollori sono in realtà così).
Ora immagina il seguente programma e la loro traduzione in un'ipotetica assemblea:
Incremento prefisso:
a = ++b + c;
; increment b
LD A, [&b]
INC A
ST A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
Incremento Postfix:
a = b++ + c;
; load b
LD A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
; increment b
LD A, [&b]
INC A
ST A, [&b]
Nota come il valore di b
stato forzato il ricaricamento . Con l'incremento del prefisso, il compilatore può semplicemente incrementare il valore e continuare ad usarlo, possibilmente evitare di ricaricarlo poiché il valore desiderato è già nel registro dopo l'incremento. Tuttavia, con l'incremento postfix, il compilatore deve fare i conti con due valori, uno il vecchio e uno il valore incrementato che, come ho mostrato sopra, comporta un ulteriore accesso alla memoria.
Naturalmente, se il valore dell'incremento non viene utilizzato, come una singola i++;
istruzione, il compilatore può (e non fa) semplicemente generare un'istruzione di incremento indipendentemente dall'uso di postfisso o prefisso.
Come nota a margine, vorrei menzionare che un'espressione in cui esiste un b++
non può essere semplicemente convertita in una ++b
senza alcuno sforzo aggiuntivo (ad esempio aggiungendo a - 1
). Quindi confrontare i due se fanno parte di qualche espressione non è realmente valido. Spesso, dove si utilizza b++
all'interno di un'espressione che non è possibile utilizzare ++b
, quindi anche se ++b
fosse potenzialmente più efficiente, sarebbe semplicemente sbagliato. L'eccezione è ovviamente se l'espressione la sta implorando (ad esempio, a = b++ + 1;
che può essere cambiata in a = ++b;
).
Ho letto attraverso la maggior parte delle risposte qui e molti dei commenti, e non ho visto alcun riferimento a un'istanza che ho potuto pensare a dove è più efficiente di (e forse sorprendentemente era più efficiente di ). Questo è per i compilatori C per il DEC PDP-11!i++
++i
--i
i--
Il PDP-11 aveva istruzioni di assemblaggio per il pre-decremento di un registro e post-incremento, ma non viceversa. Le istruzioni consentivano di utilizzare qualsiasi registro "generico" come puntatore di stack. Quindi, se hai usato qualcosa del genere *(i++)
, potrebbe essere compilato in una singola istruzione di assemblaggio, mentre *(++i)
non è possibile.
Questo è ovviamente un esempio molto esoterico, ma fornisce l'eccezione in cui il post-incremento è più efficiente (o dovrei dire lo è , dal momento che non c'è molta richiesta di codice C PDP-11 in questi giorni).
--i
e i++
.
Preferisco sempre il pre-incremento, tuttavia ...
Volevo sottolineare che anche nel caso di chiamare la funzione operator ++, il compilatore sarà in grado di ottimizzare il temporaneo se la funzione viene incorporata. Poiché l'operatore ++ è in genere breve e spesso implementato nell'intestazione, è probabile che sia inline.
Quindi, ai fini pratici, probabilmente non c'è molta differenza tra le prestazioni delle due forme. Tuttavia, preferisco sempre il pre-incremento poiché sembra meglio esprimere direttamente ciò che sto cercando di dire, piuttosto che fare affidamento sull'ottimizzatore per capirlo.
Inoltre, dare meno all'optmizer probabilmente significa che il compilatore funziona più velocemente.
La mia C è un po 'arrugginita, quindi mi scuso in anticipo. Rapidamente, posso capire i risultati. Ma sono confuso su come entrambi i file siano usciti nello stesso hash MD5. Forse un ciclo for funziona allo stesso modo, ma le seguenti 2 righe di codice non genererebbero assembly diversi?
myArray[i++] = "hello";
vs
myArray[++i] = "hello";
Il primo scrive il valore nell'array, quindi incrementa i. I secondi incrementi quindi scrivo sull'array. Non sono un esperto di assemblaggio, ma non vedo come lo stesso eseguibile sarebbe generato da queste 2 diverse righe di codice.
Solo i miei due centesimi.
foo[i++]
a foo[++i]
senza cambiare nient'altro cambierebbe ovviamente la semantica del programma, ma su alcuni processori quando si utilizza un compilatore senza una logica di ottimizzazione di sollevamento del ciclo, l'incremento p
e l' q
esecuzione di un ciclo che esegue, ad esempio, *(p++)=*(q++);
sarebbe più veloce dell'uso di un ciclo che esegue *(++pp)=*(++q);
. Per loop molto stretti su alcuni processori, la differenza di velocità può essere significativa (oltre il 10%), ma è probabilmente l'unico caso in C in cui il post-incremento è materialmente più veloce del pre-incremento.