Domanda

C'è una differenza di prestazioni tra i++ E ++i se il valore risultante non viene utilizzato?

È stato utile?

Soluzione

Sintesi:NO.

i++ potrebbe potenzialmente essere più lento di ++i, poiché il vecchio valore di iPotrebbe essere necessario salvare per un uso successivo, ma in pratica tutti i compilatori moderni lo ottimizzeranno.

Possiamo dimostrarlo guardando il codice per questa funzione, entrambi con ++i E 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, tranne ++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

Altri suggerimenti

Da Efficienza contro intenzione di Andrew Koenig:

Innanzitutto, questo non è affatto ovvio ++i è più efficiente di i++, almeno per quanto riguarda le variabili intere.

E :

Quindi la domanda che ci si dovrebbe porre non è quale di queste due operazioni è più veloce, ma quale di queste due operazioni esprime in modo più accurato ciò che si sta cercando di realizzare.Ritengo che se non si utilizza il valore dell'espressione, non c'è mai un motivo per utilizzarlo i++ invece di ++i, perché non c'è mai motivo di copiare il valore di una variabile, incrementare la variabile e quindi eliminare 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.

Una risposta migliore è questa ++i a volte sarà più veloce ma mai più lento.

Sembra che tutti lo presuppongano i è un tipo integrato regolare come int.In questo caso non ci sarà alcuna differenza misurabile.

Tuttavia se i è di tipo complesso, potresti trovare una differenza misurabile.Per i++ devi fare una copia della tua classe prima di incrementarla.A seconda di ciò che comporta una copia, potrebbe effettivamente essere più lenta rispetto a with ++it 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 è quella con ++i hai la possibilità di restituire un riferimento invece di un valore.Ancora una volta, a seconda di cosa comporta la creazione di una copia del tuo oggetto, l'operazione potrebbe essere più lenta.

Un esempio reale di dove ciò può verificarsi sarebbe l'uso degli iteratori.È improbabile che la copia di un iteratore costituisca un collo di bottiglia nella tua applicazione, ma è comunque buona pratica prendere l'abitudine di usare ++i invece di i++ dove il risultato non viene influenzato.

Prendendo spunto da Scott Meyers, C++ più efficace Articolo 6:Distinguere tra forme prefisse e suffisse delle operazioni di incremento e decremento.

La versione con prefisso è sempre preferita rispetto a quella suffissa per quanto riguarda gli oggetti, soprattutto per quanto riguarda gli iteratori.

Il motivo di ciò se si guarda allo schema di chiamata degli operatori.

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

// Postfix
const Integer Integer::operator++(int)
{
    Integer oldValue = *this;
    ++(*this);
    return oldValue;
}

Osservando questo esempio è facile vedere come l'operatore prefisso sarà sempre più efficiente del suffisso.A causa della necessità di un oggetto temporaneo nell'uso del suffisso.

Questo è il motivo per cui quando vedi esempi che utilizzano gli iteratori usano sempre la versione del prefisso.

Ma come fai notare per int, in realtà non c'è alcuna differenza a causa dell'ottimizzazione del compilatore che può avvenire.

Ecco un'ulteriore osservazione se sei preoccupato per la microottimizzazione.I cicli di decremento possono "possibilmente" essere più efficienti dei cicli di incremento (a seconda dell'architettura del set di istruzioni, ad es.BRACCIO), dato:

for (i = 0; i < 100; i++)

Su ogni ciclo avrai un'istruzione ciascuno per:

  1. Aggiunta 1 A i.
  2. Confronta se i è inferiore a a 100.
  3. Un ramo condizionale se i è inferiore a a 100.

Mentre un ciclo decrementale:

for (i = 100; i != 0; i--)

Il ciclo avrà un'istruzione per ciascuno di:

  1. Decremento i, impostando il flag di stato del registro della CPU.
  2. Un ramo condizionale che dipende dallo stato del registro della CPU (Z==0).

Naturalmente questo funziona solo quando si decrementa a zero!

Ricordato dalla Guida per gli sviluppatori del sistema ARM.

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 è la differenza tra ++i contro i++ ha senso solo all'interno dell'espressione in cui si trova.

In caso di for(i=0; i<n; i++), IL i++ è solo nella sua stessa espressione:c'è un punto di sequenza prima di i++ e ce n'è uno dopo.Pertanto l'unico codice macchina generato è "increment i di 1" ed è ben definito come questo viene sequenziato rispetto al resto del programma.Quindi, se lo cambiassi in prefix ++, non avrebbe alcuna importanza, otterresti comunque solo il codice macchina "aumenta i di 1".

Le differenze tra ++i E i++ conta solo in espressioni come array[i++] = x; contro array[++i] = x;.Alcuni potrebbero obiettare e dire che il suffisso sarà più lento in tali operazioni perché il registro dove i i residenti devono essere ricaricati in seguito.Ma tieni presente che il compilatore è libero di ordinare le tue istruzioni nel modo che preferisce, purché non "interrompa il comportamento della macchina astratta" come lo chiama lo standard C.

Quindi, anche se puoi presumerlo array[i++] = x; viene tradotto in codice macchina come:

  • Conservare il valore di i nel registro A.
  • Memorizza l'indirizzo dell'array nel registro B.
  • Aggiungi A e B, memorizza i risultati in A.
  • In questo nuovo indirizzo rappresentato da A, memorizza il valore di x.
  • Conservare il valore di i nel registro A // inefficiente perché qui sono presenti istruzioni aggiuntive, l'abbiamo già fatto una volta.
  • Registro di incremento A.
  • Memorizzare il registro A in i.

il compilatore potrebbe anche produrre il codice in modo più efficiente, come ad esempio:

  • Conservare il valore di i nel registro A.
  • Memorizza l'indirizzo dell'array nel registro B.
  • Aggiungi A e B, memorizza i risultati in B.
  • Registro di incremento A.
  • Memorizzare il registro A in i.
  • ...// resto del codice.

Solo perché tu come programmatore C sei addestrato a pensare che il suffisso ++ avviene alla fine, non è necessario ordinare il codice macchina in questo modo.

Quindi non c'è differenza tra prefisso e postfisso ++ in C.Ora, ciò di cui tu come programmatore C dovresti essere diverso sono le persone che usano in modo incoerente il prefisso in alcuni casi e il suffisso in altri casi, senza alcuna motivazione logica.Ciò suggerisce che sono incerti su come funziona il C o che hanno una conoscenza errata del linguaggio.Questo è sempre un brutto segno, perché a sua volta suggerisce che stanno prendendo altre decisioni discutibili nel loro programma, basate sulla superstizione o su "dogmi religiosi".

"Prefisso ++ è sempre più veloce" è infatti uno di questi falsi dogmi comune tra gli aspiranti programmatori C.

Per favore, non lasciare che la domanda "quale sia più veloce" sia il fattore decisivo su quale utilizzare.È probabile che non ti importerà mai più di tanto e inoltre, il tempo di lettura del programmatore è molto più costoso del tempo della macchina.

Utilizzare quello che ha più senso per l'essere umano che legge il codice.

Prima di tutto:La differenza tra i++ E ++i è trascurabile in C.


Ai dettagli.

1.Il noto problema del C++: ++i è più veloce

Nel C++, ++i è più efficiente se e solo se i è una sorta di oggetto con un operatore di incremento sovraccaricato.

Perché?
In ++i, l'oggetto viene prima incrementato e successivamente può essere passato come riferimento const a qualsiasi altra funzione.Ciò non è possibile se l'espressione lo è foo(i++) perché ora l'incremento deve essere fatto prima foo() viene chiamato, ma è necessario passare il vecchio valore foo().Di conseguenza, il compilatore è costretto a farne una copia i prima di eseguire l'operatore di incremento sull'originale.Le chiamate aggiuntive al costruttore/distruttore sono la parte negativa.

Come notato sopra, questo non si applica ai tipi fondamentali.

2.Il fatto poco noto: i++ Maggio essere più veloce

Se non è necessario chiamare alcun costruttore/distruttore, come è sempre il caso in C, ++i E i++ dovrebbe essere altrettanto veloce, giusto?NO.Sono praticamente altrettanto veloci, ma potrebbero esserci piccole differenze, che la maggior parte degli altri rispondenti ha capito male.

Come può i++ essere più veloce?
Il punto sono le dipendenze dai dati.Se il valore deve essere caricato dalla memoria, è necessario eseguire due operazioni successive con esso, incrementandolo e utilizzandolo.Con ++i, è necessario eseguire l'incremento Prima il valore può essere utilizzato.Con i++, l'utilizzo non dipende dall'incremento e la CPU può eseguire l'operazione di utilizzo in parallelo all'operazione di incremento.La differenza è al massimo di un ciclo della CPU, quindi è davvero trascurabile, ma c'è.Ed è il contrario di quanto molti si aspetterebbero.

@Mark anche se il compilatore è autorizzato a ottimizzare la copia temporanea (basata su stack) della variabile e GCC (nelle versioni recenti) non significa, non significa Tutto 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 abbia ragione, soprattutto se il codice forse più veloce, ma mai più lento, è altrettanto facile da leggere.

Se non hai un'implementazione davvero stupida di uno degli operatori nel tuo codice:

Ho sempre preferito ++i rispetto a i++.

In C, il compilatore generalmente può ottimizzarli in modo che siano uguali se il risultato non viene utilizzato.

Tuttavia, in C++ se si utilizzano altri tipi che forniscono i propri operatori ++, è probabile che la versione con prefisso sia più veloce della versione con suffisso.Quindi, se non è necessaria la semantica del suffisso, è meglio utilizzare l'operatore del prefisso.

Posso pensare a una situazione in cui il suffisso è più lento dell'incremento del prefisso:

Immagina un processore con registro A viene utilizzato come accumulatore ed è l'unico registro utilizzato in molte istruzioni (alcuni piccoli microcontrollori sono effettivamente così).

Immaginiamo ora il seguente programma e la loro traduzione in un ipotetico assembly:

Incremento del 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 suffisso:

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 costretto a essere ricaricato.Con l'incremento del prefisso, il compilatore può semplicemente incrementare il valore e procedere con l'utilizzo, possibilmente evitare di ricaricarlo poiché il valore desiderato è già nel registro dopo l'incremento.Tuttavia, con l'incremento suffisso, il compilatore deve gestire due valori, uno vecchio e uno incrementato che, come mostrato sopra, comporta un ulteriore accesso alla memoria.

Naturalmente, se il valore dell'incremento non viene utilizzato, ad esempio un singolo i++; istruzione, il compilatore può (e lo fa) semplicemente generare un'istruzione di incremento indipendentemente dall'utilizzo del suffisso o del prefisso.


Come nota a margine, vorrei menzionare che un'espressione in cui è presente a b++ non può essere semplicemente convertito in uno con ++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 usi b++ all'interno di un'espressione che non puoi utilizzare ++b, quindi anche se ++b fossero potenzialmente più efficienti, sarebbe semplicemente sbagliato.L'eccezione è ovviamente se l'espressione lo richiede (ad esempio a = b++ + 1; che può essere modificato in a = ++b;).

Preferisco sempre il pre-incremento, tuttavia...

Volevo sottolineare che anche nel caso di chiamata alla funzione operator++, il compilatore sarà in grado di ottimizzare il temporaneo se la funzione viene incorporata.Poiché l'operatore++ è solitamente breve e spesso implementato nell'intestazione, è probabile che venga incorporato.

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 all'ottimizzatore meno cose da fare probabilmente significa che il compilatore verrà eseguito più velocemente.

La mia C è un po' arrugginita, quindi mi scuso in anticipo.Per quanto riguarda la velocità, posso capire i risultati.Ma sono confuso su come entrambi i file siano arrivati ​​allo stesso hash MD5.Forse un ciclo for funziona allo stesso modo, ma le seguenti 2 righe di codice non genererebbero un assembly diverso?

myArray[i++] = "hello";

contro

myArray[++i] = "hello";

Il primo scrive il valore nell'array, quindi incrementa i.Il secondo incrementa quindi scrive nell'array.Non sono un esperto di assemblaggio, ma semplicemente non vedo come lo stesso eseguibile potrebbe essere generato da queste 2 diverse righe di codice.

Solo i miei due centesimi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top