X + = a è più veloce di x = x + a?


84

Stavo leggendo "The C ++ Programming Language" di Stroustrup, dove dice che tra due modi per aggiungere qualcosa a una variabile

x = x + a;

e

x += a;

Preferisce +=perché molto probabilmente è meglio implementato. Penso che voglia dire che funziona anche più velocemente.
Ma lo è davvero? Se dipende dal compilatore e da altre cose, come posso controllare?


45
"The C ++ Programming Language" è stato pubblicato per la prima volta nel 1985. La versione più recente è stata pubblicata nel 1997 e un'edizione speciale della versione del 1997 è stata pubblicata nel 2000. Di conseguenza, alcune parti sono enormemente obsolete.
JoeG

5
Le due linee potrebbero potenzialmente fare qualcosa di completamente diverso. Devi essere più specifico.
Kerrek SB


26
I compilatori moderni sono abbastanza intelligenti da considerare queste domande "obsolete".
gd1

2
Riaperto questo perché la domanda duplicata chiede di C non C ++.
Kev

Risposte:


212

Ogni compilatore degno genererà esattamente la stessa sequenza di linguaggio macchina per entrambi i costrutti per qualsiasi tipo built-in ( int, float, ecc) fino a quando l'affermazione è davvero così semplice come x = x + a; e ottimizzazione è abilitato . (In particolare, GCC -O0, che è la modalità predefinita, esegue anti-ottimizzazioni , come l'inserimento di archivi completamente non necessari in memoria, al fine di garantire che i debugger possano sempre trovare i valori delle variabili.)

Se l'affermazione è più complicata, però, potrebbero essere diverse. Supponiamo che fsia una funzione che restituisce un puntatore, quindi

*f() += a;

chiama fsolo una volta, mentre

*f() = *f() + a;

lo chiama due volte. Se fha effetti collaterali, uno dei due sarà sbagliato (probabilmente quest'ultimo). Anche se fnon ha effetti collaterali, il compilatore potrebbe non essere in grado di eliminare la seconda chiamata, quindi quest'ultima potrebbe effettivamente essere più lenta.

E poiché qui stiamo parlando di C ++, la situazione è completamente diversa per i tipi di classe che sovraccaricano operator+e operator+=. Se xè un tale tipo, allora, prima dell'ottimizzazione, si x += atraduce in

x.operator+=(a);

mentre si x = x + atraduce in

auto TEMP(x.operator+(a));
x.operator=(TEMP);

Ora, se la classe è scritta correttamente e l'ottimizzatore del compilatore è abbastanza buono, entrambi finiranno per generare lo stesso linguaggio macchina, ma non è una cosa sicura come lo è per i tipi incorporati. Questo è probabilmente ciò a cui sta pensando Stroustrup quando ne incoraggia l'uso +=.


14
C'è anche un altro aspetto: la leggibilità . Il linguaggio C ++ per aggiungere expra varis var+=expre scriverlo in un altro modo confonderà i lettori.
Tadeusz Kopec

21
Se ti ritrovi a scrivere *f() = *f() + a;, potresti voler dare una buona occhiata a ciò che stai veramente cercando di ottenere ...
Adam Davis

3
E se var = var + expr ti confonde, ma var + = expr non lo fa, sei l'ingegnere del software più strano che abbia mai incontrato. Entrambi sono leggibili; assicurati solo di essere coerente (e tutti usiamo op =, quindi è comunque discutibile = P)
WhozCraig

7
@PiotrDobrogost: Cosa c'è di sbagliato nel rispondere alle domande? In ogni caso, dovrebbe essere l'interlocutore a controllare i duplicati.
Gorpik

4
@PiotrDobrogost mi sembra che tu sia un po '... geloso ... Se vuoi andare in giro a cercare duplicati, fallo. Io, per esempio, preferisco rispondere alle domande piuttosto che cercare stupidi (a meno che non sia una domanda che ricordo specificamente di aver visto prima). A volte può essere più veloce ed ergo aiutare chi ha posto la domanda più velocemente. Inoltre, nota che questo non è nemmeno un loop. 1è una costante, apuò essere un tipo volatile, definito dall'utente o qualsiasi altra cosa. Completamente differente. In effetti, non capisco nemmeno come sia stato chiuso.
Luchian Grigore

56

Puoi controllare guardando il disassemblaggio, che sarà lo stesso.

Per i tipi di base , entrambi sono ugualmente veloci.

Questo è l'output generato da una build di debug (ovvero nessuna ottimizzazione):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

Per i tipi definiti dall'utente , dove è possibile eseguire l'overload operator +e operator +=, dipende dalle rispettive implementazioni.


1
Non è vero in tutti i casi. Ho scoperto che può essere più veloce caricare un indirizzo di memoria in un registro, incrementarlo e riscriverlo piuttosto che incrementare direttamente la posizione di memoria (senza usare atomici). Proverò a raccogliere il codice ...
James

E i tipi definiti dall'utente? I buoni compilatori dovrebbero generare un assembly equivalente, ma non esiste tale garanzia.
mfontanini

1
@LuchianGrigore No, se aè 1 ed xè volatileil compilatore potrebbe generare inc DWORD PTR [x]. Questo è lento.
James,

1
@Chiffa non dipendente dal compilatore, ma dipendente dallo sviluppatore. Potresti implementare operator +per non fare nulla e operator +=calcolare il 100000esimo numero primo e poi tornare. Certo, sarebbe una cosa stupida da fare, ma è possibile.
Luchian Grigore

3
@ James: se il tuo programma è sensibile alla differenza di prestazioni tra ++xe temp = x + 1; x = temp;, allora molto probabilmente dovrebbe essere scritto in assembly anziché in c ++ ...
EmirCalabuch

11

Sì! È più veloce da scrivere, più veloce da leggere e più veloce da capire, per quest'ultimo nel caso che xpotrebbe avere effetti collaterali. Quindi è nel complesso più veloce per gli umani. Il tempo umano in generale costa molto di più del tempo del computer, quindi deve essere quello che stavi chiedendo. Destra?


8

Dipende davvero dal tipo di xe a e dall'implementazione di +. Per

   T x, a;
   ....
   x = x + a;

il compilatore deve creare una T temporanea per contenere il valore di x + a mentre lo valuta, che può poi assegnare a x. (Non può utilizzare xo a come spazio di lavoro durante questa operazione).

Per x + = a, non è necessario un temporaneo.

Per i tipi banali, non c'è differenza.


8

La differenza tra x = x + ae x += aè la quantità di lavoro che la macchina deve affrontare: alcuni compilatori possono (e di solito lo fanno) ottimizzarla, ma di solito, se ignoriamo l'ottimizzazione per un po ', ciò che accade è che nel primo frammento di codice, il macchina deve cercare il valore per xdue volte, mentre in quest'ultimo, questa ricerca deve essere eseguita una sola volta.

Tuttavia, come ho detto, oggi la maggior parte dei compilatori è abbastanza intelligente da analizzare l'istruzione e ridurre le istruzioni macchina richieste.

PS: prima risposta su Stack Overflow!


6

Come hai etichettato questo C ++, non c'è modo di saperlo dalle due dichiarazioni che hai pubblicato. Devi sapere cos'è "x" (è un po 'come la risposta "42"). Se xè un POD, allora non farà molta differenza. Tuttavia, se xè una classe, potrebbero esserci sovraccarichi per i metodi operator +e operator +=che potrebbero avere comportamenti diversi che portano a tempi di esecuzione molto diversi.


6

Se dici +=che stai rendendo la vita molto più facile per il compilatore. Affinché il compilatore riconosca che x = x+aè lo stesso di x += a, il compilatore deve farlo

  • analizzare il lato sinistro ( x) per assicurarsi che non abbia effetti collaterali e si riferisca sempre allo stesso valore l. Ad esempio, potrebbe essere z[i], e deve assicurarsi che entrambi ze inon cambino.

  • analizza il lato destro ( x+a) e assicurati che sia una somma, e che il lato sinistro ricorra una e solo una volta sul lato destro, anche se potrebbe essere trasformato, come in z[i] = a + *(z+2*0+i).

Se quello che vuoi dire è quello di aggiungere aal x, lo scrittore compilatore apprezza quando basta dire quello che vuoi dire. In questo modo, non stai esercitando la parte del compilatore da cui il suo autore spera che abbia tolto tutti i bug, e questo in realtà non ti rende la vita più facile, a meno che tu non possa onestamente tirarti fuori della modalità Fortran.


5

Per un esempio concreto, immagina un semplice tipo di numero complesso:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

Per il caso a = a + b, deve creare una variabile temporanea aggiuntiva e quindi copiarla.


questo è un esempio molto carino, mostra come sono stati implementati 2 operatori.
Grijesh Chauhan

5

Stai facendo la domanda sbagliata.

È improbabile che ciò determini le prestazioni di un'app o di una funzionalità. Anche se lo fosse, il modo per scoprirlo è profilare il codice e sapere come ti influenza con certezza. Invece di preoccuparsi a questo livello di ciò che è più veloce, è molto più importante pensare in termini di chiarezza, correttezza e leggibilità.

Ciò è particolarmente vero se si considera che, anche se questo è un fattore di prestazioni significativo, i compilatori si evolvono nel tempo. Qualcuno potrebbe trovare una nuova ottimizzazione e la risposta giusta oggi può diventare sbagliata domani. È un classico caso di ottimizzazione prematura.

Questo non vuol dire che le prestazioni non contino affatto ... Solo che è l'approccio sbagliato per raggiungere i tuoi obiettivi di prestazione. L'approccio giusto consiste nell'utilizzare strumenti di profilazione per capire dove il tuo codice sta effettivamente spendendo il suo tempo e quindi dove concentrare i tuoi sforzi.


Certo, ma era intesa come una domanda di basso livello, non un quadro generale "Quando dovrei considerare una tale differenza".
Chiffa

1
La domanda del PO era del tutto legittima, come dimostrato dalle risposte (e dai voti positivi) di altri. Anche se noi otteniamo il vostro punto (profilo prima, etc.) sicuramente è interessante sapere questo genere di cose - sei davvero al profilo ogni affermazione banale che si scrive, il profilo dei risultati di ogni decisione si prende? Anche quando ci sono persone su SO che hanno già studiato, profilato, smontato il caso e hanno una risposta generale da fornire?

4

Penso che dovrebbe dipendere dalla macchina e dalla sua architettura. Se la sua architettura consente l'indirizzamento indiretto della memoria, il writer del compilatore POTREBBE semplicemente utilizzare questo codice (per l'ottimizzazione):

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

Considerando che, i = i + ypotrebbe essere tradotto in (senza ottimizzazione):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


Detto questo, idovrebbero essere pensate anche altre complicazioni come se è una funzione che restituisce un puntatore, ecc. La maggior parte dei compilatori a livello di produzione, incluso GCC, produce lo stesso codice per entrambe le istruzioni (se sono numeri interi).


2

No, entrambi i modi vengono gestiti allo stesso modo.


10
No, se è un tipo definito dall'utente con operatori sovraccarichi.
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.