Evita l'operatore di incremento postfisso

26

Ho letto che dovrei evitare l'operatore di incremento postfisso a causa di motivi di prestazioni (in alcuni casi) .

Ma questo non influisce sulla leggibilità del codice? A mio parere:

for(int i = 0; i < 42; i++);
    /* i will never equal 42! */

Sembra migliore di:

for(int i = 0; i < 42; ++i);
    /* i will never equal 42! */

Ma probabilmente è solo per abitudine. Devo ammettere che non ho visto molti usare ++i .

In questo caso le prestazioni sono così difficili da sacrificare alla leggibilità? O sono solo cieco e ++i è più leggibile di i++ ?

    
posta Mateen Ulhaq 19.03.2011 - 07:59
fonte

11 risposte

56

I fatti:

  1. i ++ e ++ i sono ugualmente facili da leggere. Non ti piace perché non ci sei abituato, ma sostanzialmente non c'è niente che tu possa interpretare male come, quindi non c'è più lavoro da leggere o scrivere.

  2. In almeno alcuni casi, l'operatore postfisso sarà meno efficiente.

  3. Tuttavia, nel 99,99% dei casi, non avrà importanza perché (a) agirà comunque su un tipo semplice o primitivo ed è solo un problema se sta copiando un oggetto grande (b) ha vinto essere in una parte critica dal punto di vista delle prestazioni (c) non sai se il compilatore lo ottimizzerà o meno, potrebbe farlo.

  4. Quindi, suggerisco di usare il prefisso a meno che non sia specificamente necessario che postfix sia una buona abitudine, solo perché (a) è buona abitudine essere precisi con altre cose e (b) una volta in una luna blu tu Intenderò usare postfix e fare il giro sbagliato: se scrivi sempre ciò che intendi, è meno probabile. C'è sempre un compromesso tra prestazioni e ottimizzazione.

Dovresti usare il buon senso e non la micro-ottimizzazione fino a quando non ne hai bisogno, ma non essere in flagrante inefficiente per il gusto di farlo. In genere ciò significa: innanzitutto, escludere qualsiasi costruzione di codice che sia inaccettabilmente inefficiente anche in un codice non critico nel tempo (normalmente qualcosa che rappresenta un errore concettuale fondamentale, come il passaggio di oggetti da 500 MB per valore senza motivo); e in secondo luogo, di ogni altro modo di scrivere il codice, scegliere il più chiaro.

Tuttavia, qui, credo che la risposta sia semplice: credo che scrivere un prefisso a meno che non si abbia specificamente bisogno di postfix sia (a) molto marginalmente più chiaro e (b) molto marginalmente più probabile sia più efficiente, quindi dovresti sempre scriverlo predefinito, ma non preoccuparti se lo dimentichi.

Sei mesi fa, ho pensato lo stesso di te, che i ++ era più naturale, ma è puramente quello a cui sei abituato.

EDIT 1: Scott Meyers, in "Più efficace C ++" di cui generalmente mi fido di questa cosa, dice che in generale si dovrebbe evitare di usare l'operatore postfisso su tipi definiti dall'utente (perché l'unica sana implementazione della funzione di incremento postfisso è per creare una copia dell'oggetto, chiamare la funzione di incremento del prefisso per eseguire l'incremento e restituire la copia, ma le operazioni di copia possono essere costose).

Quindi, non sappiamo se ci sono delle regole generali su (a) se ciò sia vero oggi, (b) se si applica anche (meno così) ai tipi intrinseci (c) se dovresti usare "+ + "su qualcosa di più di una lezione di iteratori leggera di sempre. Ma per tutte le ragioni che ho descritto sopra, non importa, fai quello che ho detto prima.

EDIT 2: si riferisce alla pratica generale. Se pensi che sia importante in qualche caso specifico, allora dovresti profilarlo e vedere. La creazione di profili è facile ed economica e funziona. Deducendo dai primi principi ciò che deve essere ottimizzato è difficile e costoso e non funziona.

    
risposta data 19.03.2011 - 21:12
fonte
59

Inserisci sempre il codice per il programmatore e poi il computer.

Se c'è una differenza di prestazioni, dopo che il compilatore ha lanciato il suo esperto sul tuo codice, E puoi misurarlo E è importante - allora puoi cambiarlo .

    
risposta data 19.03.2011 - 15:35
fonte
14

GCC produce lo stesso codice macchina per entrambi i loop.

Codice C

int main(int argc, char** argv)
{
    for (int i = 0; i < 42; i++)
            printf("i = %d\n",i);

    for (int i = 0; i < 42; ++i)
        printf("i = %d\n",i);

    return 0;
}

Codice di assieme (con i miei commenti)

    cstring
LC0:
    .ascii "i = %d
int main(int argc, char** argv)
{
    for (int i = 0; i < 42; i++)
            printf("i = %d\n",i);

    for (int i = 0; i < 42; ++i)
        printf("i = %d\n",i);

    return 0;
}
" .text .globl _main _main: pushl %ebp movl %esp, %ebp pushl %ebx subl $36, %esp call L9 "L00000000001$pb": L9: popl %ebx movl $0, -16(%ebp) // -16(%ebp) is "i" for the first loop jmp L2 L3: movl -16(%ebp), %eax // move i for the first loop to the eax register movl %eax, 4(%esp) // push i onto the stack leal LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register movl %eax, (%esp) // push the address of the format string onto the stack call L_printf$stub // call printf leal -16(%ebp), %eax // make the eax register point to i incl (%eax) // increment i L2: cmpl $41, -16(%ebp) // compare i to the number 41 jle L3 // jump to L3 if less than or equal to 41 movl $0, -12(%ebp) // -12(%ebp) is "i" for the second loop jmp L5 L6: movl -12(%ebp), %eax // move i for the second loop to the eax register movl %eax, 4(%esp) // push i onto the stack leal LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register movl %eax, (%esp) // push the address of the format string onto the stack call L_printf$stub // call printf leal -12(%ebp), %eax // make eax point to i incl (%eax) // increment i L5: cmpl $41, -12(%ebp) // compare i to 41 jle L6 // jump to L6 if less than or equal to 41 movl $0, %eax addl $36, %esp popl %ebx leave ret .section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5 L_printf$stub: .indirect_symbol _printf hlt ; hlt ; hlt ; hlt ; hlt .subsections_via_symbols
    
risposta data 20.03.2011 - 19:10
fonte
11

Don't worry about performance, say 97% of the time. Premature Optimization is the Root of all Evil.

-- Donald Knuth

Ora che è fuori dai piedi, facciamo la nostra scelta sanely :

  • ++i : incremento prefisso , incrementa il valore corrente e produce il risultato
  • i++ : incremento postfisso , copia il valore, incrementa il valore corrente, produce la copia

A meno che non sia richiesta una copia del vecchio valore, usare incremento postfisso è un modo per ottenere risultati.

L'imprecisione deriva dalla pigrizia, usa sempre il costrutto che esprime il tuo intento nel modo più diretto, ci sono meno possibilità che il futuro manutentore possa fraintendere il tuo intento originale.

Anche se è (davvero) minore qui, ci sono momenti in cui sono rimasto davvero perplesso leggendo il codice: mi chiedevo davvero se l'intento e l'espressione reale coincidessero, e, naturalmente, dopo alcuni mesi, loro ( o I) non ricordo neanche ...

Quindi, non importa se ti sembra giusto o no. Abbraccia KISS . Tra pochi mesi avrai evitato le tue vecchie pratiche.

    
risposta data 20.03.2011 - 20:47
fonte
4

In C ++, potresti fare una sostanziale differenza di prestazioni se ci sono sovraccarichi dell'operatore, specialmente se stai scrivendo un codice basato su modelli e non sai quali potrebbero essere passati gli iteratori. La logica dietro qualsiasi iteratore X può essere sia sostanziale che significativo, ovvero lento e non ottimizzabile dal compilatore.

Ma questo non è il caso in C, dove sai che sarà solo un tipo banale, e la differenza di prestazioni è banale e il compilatore può facilmente ottimizzare.

Quindi un suggerimento: programma in C, o in C ++, e le domande riguardano l'una o l'altra, non entrambe.

    
risposta data 19.03.2011 - 17:19
fonte
2

Le prestazioni di entrambe le operazioni dipendono strongmente dall'architettura sottostante. Si deve incrementare un valore che è memorizzato in memoria, il che significa che il collo di bottiglia di von Neumann è il fattore limitante in entrambi i casi.

Nel caso di ++ i, dobbiamo

Fetch i from memory 
Increment i
Store i back to memory
Use i

Nel caso di i ++, dobbiamo

Fetch i from memory
Use i
Increment i
Store i back to memory

Gli operatori ++ e - tracciano la loro origine al set di istruzioni PDP-11. Il PDP-11 potrebbe eseguire il post-incremento automatico su un registro. Potrebbe anche eseguire il pre-decremento automatico su un indirizzo effettivo contenuto in un registro. In entrambi i casi, il compilatore poteva trarre vantaggio da queste operazioni a livello macchina solo se la variabile in questione era una variabile "registro".

    
risposta data 19.03.2011 - 15:18
fonte
2

Se vuoi sapere se qualcosa è lento, testalo. Prendi un BigInteger o equivalente, inseriscilo in un loop simile per entrambi gli idiomi, assicurati che l'interno del loop non venga ottimizzato e dedica loro entrambi.

Dopo aver letto l'articolo, non lo trovo molto convincente, per tre motivi. Uno, il compilatore dovrebbe essere in grado di ottimizzare la creazione di un oggetto che non viene mai utilizzato. Due, il concetto i++ è idiomatico per numeric for loops , quindi i casi che posso vedere effettivamente interessati sono limitati a. Tre, forniscono un argomento puramente teorico, senza numeri di supporto.

In base alla ragione numero 1, in particolare, suppongo che quando esegui effettivamente i tempi, saranno proprio uno accanto all'altro.

    
risposta data 19.03.2011 - 15:36
fonte
-1

Innanzitutto non influisce sulla leggibilità IMO. Non è quello che sei abituato a vedere, ma ci vorrebbe poco prima che ti abitui.

Secondo, a meno che tu non usi un sacco di operatori postfix nel tuo codice, probabilmente non vedrai molta differenza. L'argomento principale per non utilizzarli quando possibile è che una copia del valore della var originale deve essere conservata fino alla fine degli argomenti in cui la var originale potrebbe ancora essere usata. È a 32 bit oa 64 bit a seconda dell'architettura. Ciò equivale a 4 o 8 byte o 0,00390625 o 0,0078125 MB. Le probabilità sono molto alte che a meno che tu non ne stia utilizzando una tonnellata che devono essere salvate per un periodo di tempo molto lungo che con le risorse e la velocità del computer di oggi non si noterà nemmeno una differenza facendo un passaggio da postfix a prefisso.

EDIT: dimentica questa parte rimanente in quanto la mia conclusione è stata dimostrata falsa (eccetto per la parte di ++ i e i ++ che non sempre fa la stessa cosa ... è ancora vero).

Inoltre è stato sottolineato in precedenza che non fanno la stessa cosa in un caso. State attenti a fare il passaggio se decidete di farlo. Non l'ho mai provato (ho sempre usato postfix) quindi non lo so per certo, ma penso che il passaggio da postfix a prefisso risulterà in risultati diversi: (di nuovo potrei sbagliarmi ... dipende anche sul compilatore / interprete)

for (int i=0; i < 10; i++) //the set of i values here will be {0,1,2,3,4,5,6,7,8,9}
for (int i=0; i < 10; ++i) //the set of i values here will be {1,2,3,4,5,6,7,8,9,10}
    
risposta data 19.03.2011 - 17:20
fonte
-1

Penso semanticamente, ++i ha più senso di i++ , quindi mi attenersi al primo, tranne è normale non farlo (come in Java, dove dovresti usa i++ perché è ampiamente utilizzato).

    
risposta data 21.03.2011 - 10:45
fonte
-2

Non si tratta solo di prestazioni.

A volte vuoi evitare di implementare la copia, perché non ha senso. E poiché l'utilizzo dell'aggiornamento del prefisso non dipende da questo, è chiaramente più semplice attenersi al modulo del prefisso.

E utilizzando diversi incrementi per tipi primitivi e tipi complessi ... è davvero illeggibile.

    
risposta data 19.03.2011 - 20:02
fonte
-2

A meno che tu non ne abbia davvero bisogno, mi attengo a ++ i. Nella maggior parte dei casi, questo è ciò che si intende. Non è molto spesso che hai bisogno di i ++, e devi sempre pensarci due volte quando leggi un simile costrutto. Con ++ i, è facile: aggiungi 1, usalo, e poi io sono sempre lo stesso.

Quindi, sono assolutamente d'accordo con @martin beckett: rendilo più facile per te stesso, è già abbastanza difficile.

    
risposta data 21.03.2011 - 14:19
fonte

Leggi altre domande sui tag