Il compilatore C ++ rimuove / ottimizza le parentesi inutili?


19

Sarà il codice

int a = ((1 + 2) + 3); // Easy to read

corri più lentamente di

int a = 1 + 2 + 3; // (Barely) Not quite so easy to read

o sono compilatori moderni abbastanza intelligenti da rimuovere / ottimizzare parentesi "inutili".

Potrebbe sembrare una minuscola preoccupazione per l'ottimizzazione, ma la scelta di C ++ su C # / Java / ... significa solo ottimizzazioni (IMHO).


9
Penso che anche C # e Java lo ottimizzeranno. Credo che quando analizzano e creano AST, rimuoveranno solo cose ovvie inutili come quella.
Farid Nouri Neshat,

5
Tutto ciò che ho letto indica facilmente la compilazione JIT, dando alla compilazione anticipata una corsa per i suoi soldi, quindi da sola non è un argomento molto convincente. Visualizzi la programmazione del gioco - il vero motivo per favorire la compilazione anticipata è che è prevedibile - con la compilazione JIT non sai mai quando inizierà il compilatore e provi ad iniziare a compilare il codice. Ma noterei che la compilazione anticipata nel codice nativo non si esclude a vicenda con la garbage collection, vedi ad esempio Standard ML e D. E ho visto argomenti convincenti che la garbage collection sia più efficiente ...
Doval

7
... rispetto a RAII e ai puntatori intelligenti, quindi è più una questione di seguire il percorso ben battuto (C ++) rispetto al percorso relativamente non svolto del fare programmazione di gioco in quei linguaggi. Vorrei anche notare che preoccuparsi delle parentesi è folle: vedo da dove vieni, ma questa è una ridicola micro-ottimizzazione. La scelta delle strutture dati e degli algoritmi nel tuo programma dominerà sicuramente le prestazioni, non tali banalità.
Doval,

6
Um ... Che tipo di ottimizzazione ti aspetti, esattamente? Se stai parlando di analisi statiche, nella maggior parte delle lingue che conosco, questo sarà sostituito dal risultato staticamente noto (le implementazioni basate su LLVM applicano persino questo, AFAIK). Se stai parlando dell'ordine di esecuzione, non importa, poiché è la stessa operazione e senza effetti collaterali. L'aggiunta richiede comunque due operandi. E se lo stai usando per confrontare C ++, Java e C # per quanto riguarda le prestazioni, sembra che tu non abbia una chiara idea di cosa siano le ottimizzazioni e come funzionano, quindi dovresti concentrarti sull'apprendimento.
Theodoros Chatzigiannakis,

5
Mi chiedo perché a) consideri l'espressione tra parentesi più leggibile (per me sembra brutto, fuorviante (perché sottolineano questo particolare ordine? Non dovrebbe essere assicurativo qui?) E goffo) b) perché dovresti pensare senza tra parentesi potrebbe funzionare meglio (analizzare chiaramente le parentesi è più facile per una macchina che dover ragionare sulle fissità dell'operatore. Come dice Marc van Leuwen, questo non ha alcuna influenza sul tempo di esecuzione).
lasciato circa

Risposte:


87

Il compilatore non inserisce o rimuove mai le parentesi; crea semplicemente un albero di analisi (in cui non sono presenti parentesi) corrispondente alla tua espressione, e nel fare ciò deve rispettare le parentesi che hai scritto. Se parentesi completamente la tua espressione, sarà anche immediatamente chiaro al lettore umano che cos'è quell'albero di analisi; se vai all'estremo di inserire parentesi palesemente ridondanti come in int a = (((0)));allora causerai uno stress inutile sui neuroni del lettore mentre sprecherai anche alcuni cicli nel parser, senza tuttavia cambiare l'albero di analisi risultante (e quindi il codice generato ) il minimo.

Se non si scrive alcuna parentesi, il parser deve ancora fare il proprio lavoro nel creare un albero di analisi e le regole per la precedenza e l'associatività dell'operatore gli dicono esattamente quale albero di analisi deve costruire. Potresti considerare quelle regole come dire al compilatore quali parentesi (implicite) dovrebbe inserire nel tuo codice, anche se in questo caso il parser in realtà non ha mai a che fare con parentesi: è stato appena costruito per produrre lo stesso albero di analisi come se le parentesi erano presenti in alcuni luoghi. Se si posizionano le parentesi esattamente in quei punti, come in int a = (1+2)+3;(associatività di +è a sinistra), il parser arriverà allo stesso risultato con un percorso leggermente diverso. Se si inseriscono parentesi diverse come inint a = 1+(2+3);quindi stai forzando un diverso albero di analisi, che potrebbe causare la generazione di codice diverso (anche se forse no, poiché il compilatore può applicare trasformazioni dopo aver creato l'albero di analisi, a condizione che l' effetto dell'esecuzione del codice risultante non sia mai diverso per esso). Supponendo che ci sia una differenza nel codice di resuting, in generale non si può dire quale sia più efficiente; il punto più importante è ovviamente che la maggior parte delle volte gli alberi di analisi non danno espressioni matematicamente equivalenti, quindi il confronto della loro velocità di esecuzione è a parte il punto: si dovrebbe semplicemente scrivere l'espressione per ottenere il risultato corretto.

Quindi il risultato è: usare le parentesi come necessario per la correttezza e come desiderato per la leggibilità; se ridondanti non hanno alcun effetto sulla velocità di esecuzione (e un effetto trascurabile sul tempo di compilazione).

E nulla di tutto ciò ha a che fare con l'ottimizzazione , che si presenta dopo la costruzione dell'albero di analisi, quindi non può sapere come è stato costruito l'albero di analisi. Questo si applica senza modifiche dal più vecchio e stupido dei compilatori a quelli più intelligenti e moderni. Solo in un linguaggio interpretato (in cui "tempo di compilazione" e "tempo di esecuzione" sono coincidenti) potrebbe esserci una penalità per le parentesi ridondanti, ma anche allora penso che la maggior parte di tali lingue sia organizzata in modo tale che almeno la fase di analisi venga eseguita una sola volta per ogni istruzione (memorizzandone una forma pre-analizzata per l'esecuzione).


s / OLDES / più vecchio /. Buona risposta, +1.
David Conrad,

23
Avvertimento totale di nitpick: "La domanda non è molto ben formulata". - Non sono d'accordo, nel senso che "ben messo" significa "suggerendo chiaramente quale gap di conoscenza il querent vuole colmare". In sostanza la domanda è "ottimizzare per X, scegliere A o B, e perché? Cosa succede sotto?", Che almeno per me suggerisce molto chiaramente qual è il divario di conoscenza. Il difetto della domanda, che giustamente si sottolinea e si affronta molto bene, è che è costruito su un modello mentale difettoso.
Jonas Kölker,

Perché a = b + c * d;, a = b + (c * d);sarebbe [innocuo] parentesi ridondanti. Se ti aiutano a rendere il codice più leggibile, va bene. a = (b + c) * d;sarebbero parentesi non ridondanti: in realtà cambiano l'albero di analisi risultante e danno un risultato diverso. Perfettamente legale da fare (in effetti, necessario), ma non sono gli stessi del raggruppamento predefinito implicito.
Phil Perry,

1
@OrangeDog è vero, è un peccato che il commento di Ben abbia fatto emergere alcune persone a cui piace affermare che le VM sono più veloci di quelle native.
gbjbaanb,

1
@ JonasKölker: La mia frase di apertura in realtà si riferisce alla domanda formulata nel titolo: non si può davvero rispondere a una domanda sul fatto che il compilatore inserisca o rimuova le parentesi, dal momento che si basa su un'idea sbagliata di come funzionano i compilatori. Ma sono d'accordo che sia abbastanza chiaro quale gap di conoscenza debba essere affrontato.
Marc van Leeuwen,

46

Le parentesi sono lì solo a tuo vantaggio, non i compilatori. Il compilatore creerà il codice macchina corretto per rappresentare la tua dichiarazione.

Cordiali saluti, il compilatore è abbastanza intelligente da ottimizzarlo completamente se possibile. Nei tuoi esempi, questo verrebbe trasformato in int a = 6;fase di compilazione.


9
Assolutamente - inserisci tutte le parentesi che vuoi e lascia che il compilatore faccia il duro lavoro di capire cosa vuoi :)
gbjbaanb,

23
La vera programmazione di @Serge riguarda più la leggibilità del codice che le prestazioni. Ti odierai l'anno prossimo quando avrai bisogno di eseguire il debug di un arresto anomalo e hai solo il codice "ottimizzato" per passare.
maniaco del cricchetto,

1
@ratchetfreak, hai ragione ma so anche come commentare il mio codice. int a = 6; // = (1 + 2) + 3
Serge

20
@Serge So che non reggerà dopo un anno di modifiche, nel tempo i commenti e il codice andranno fuori sincrono e poi finirai conint a = 8;// = 2*3 + 5
maniaco del cricchetto

21
o da www.thedailywtf.com:int five = 7; //HR made us change this to six...
Mooing Duck

23

La risposta alla domanda che hai effettivamente posto è no, ma la risposta alla domanda che intendevi porre è sì. L'aggiunta di parentesi non rallenta il codice.

Hai fatto una domanda sull'ottimizzazione, ma le parentesi non hanno nulla a che fare con l'ottimizzazione. Il compilatore applica una varietà di tecniche di ottimizzazione con l'intenzione di migliorare la dimensione o la velocità del codice generato (a volte entrambi). Ad esempio, potrebbe prendere l'espressione A ^ 2 (A al quadrato) e sostituirla con A x A (A moltiplicata per se stessa) se è più veloce. La risposta qui è no, il compilatore non fa nulla di diverso nella sua fase di ottimizzazione a seconda che non siano presenti parentesi.

Penso che intendevi chiedere se il compilatore genera ancora lo stesso codice se aggiungi parentesi non necessarie a un'espressione, in luoghi che ritieni possano migliorare la leggibilità. In altre parole, se aggiungi parentesi il compilatore è abbastanza intelligente da eliminarle di nuovo piuttosto che generare in qualche modo codice più scadente. La risposta è sì, sempre.

Lasciami dire con attenzione. Se si aggiungono parentesi a un'espressione che sono strettamente inutili (non hanno alcun effetto sul significato o sull'ordine di valutazione di un'espressione), il compilatore le eliminerà silenziosamente e genererà lo stesso codice.

Tuttavia, esistono alcune espressioni in cui parentesi apparentemente non necessarie cambieranno effettivamente l'ordine di valutazione di un'espressione e in tal caso il compilatore genererà codice per attuare ciò che hai effettivamente scritto, che potrebbe essere diverso da quello che intendevi. Ecco un esempio Non farlo!

short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c;    // ok
int d = (-a + b) + c;  // ok, same code
int d = (-a + b + c);  // ok, same code
int d = ((((-a + b)) + c));  // ok, same code
int d = -a + (b + c);  // undefined behaviour, different code

Quindi aggiungi parentesi se vuoi, ma assicurati che non siano davvero necessarie!

Non lo faccio mai. Vi è il rischio di errore senza vantaggi reali.


Nota a piè di pagina: il comportamento senza segno si verifica quando un'espressione intera con segno restituisce un valore al di fuori dell'intervallo che può esprimere, in questo caso da -32767 a +32767. Questo è un argomento complesso, fuori dalla portata di questa risposta.


Il comportamento indefinito nell'ultima riga è perché un short con segno ha solo 15 bit dopo il segno, quindi una dimensione massima di 32767, giusto? In quel banale esempio, il compilatore dovrebbe avvertire di overflow, giusto? +1 per un contro esempio, in entrambi i casi. Se fossero parametri di una funzione, non riceveresti un avviso. Inoltre, se apuò davvero essere senza segno, condurre il calcolo con -a + bpotrebbe facilmente traboccare, se afosse negativo e bpositivo.
Patrick M,

@PatrickM: vedi modifica. Comportamento indefinito significa che il compilatore può fare ciò che gli piace, inclusa l'emissione di un avviso o meno. L'aritmetica senza segno non produce UB, ma viene ridotta al modulo con la successiva potenza maggiore di due.
david.pfx,

L'espressione (b+c)nell'ultima riga promuoverà i suoi argomenti int, quindi a meno che il compilatore non definisca int16 bit (o perché è antico o indirizza un piccolo microcontrollore) l'ultima riga sarebbe perfettamente legittima.
supercat,

@supercat: io non la penso così. Il tipo e il tipo comuni del risultato devono essere brevi int. Se non è qualcosa che ti è mai stato chiesto, forse ti piacerebbe pubblicare una domanda?
david.pfx,

@ david.pfx: Le regole delle promozioni aritmetiche sono abbastanza chiare: tutto ciò che è più piccolo di quello che intviene promosso a intmeno che quel tipo non sia in grado di rappresentare tutti i suoi valori nel qual caso viene promosso unsigned int. I compilatori possono omettere le promozioni se tutti i comportamenti definiti sarebbero gli stessi se le promozioni fossero incluse . Su una macchina in cui i tipi a 16 bit si comportano come un anello algebrico astratto avvolgente, (a + b) + c e a + (b + c) saranno equivalenti. Se intfosse un tipo a 16 bit intrappolato in overflow, tuttavia, ci sarebbero casi in cui una delle espressioni ...
supercat

7

Le parentesi sono lì solo per te per manipolare l'ordine di precedenza dell'operatore. Una volta compilate, le parentesi non esistono più perché il runtime non ne ha bisogno. Il processo di compilazione rimuove tutte le parentesi, gli spazi e gli altri zuccheri sintattici di cui io e te abbiamo bisogno e trasforma tutti gli operatori in qualcosa di [molto] più semplice da eseguire per il computer.

Quindi, dove io e te potremmo vedere ...

  • "int a = ((1 + 2) + 3);"

... un compilatore potrebbe emettere qualcosa di più simile a questo:

  • Char [1] :: "a"
  • Int32 :: DeclareStackVariable ()
  • Int32 :: 0x00000001
  • Int32 :: 0x00000002
  • Int32 :: add ()
  • Int32 :: 0x00000003
  • Int32 :: add ()
  • Int32 :: AssignToVariable ()
  • vuoto :: DiscardResult ()

Il programma viene eseguito iniziando dall'inizio ed eseguendo ciascuna istruzione a turno.
La precedenza dell'operatore è ora "primo arrivato, primo servito".
Tutto è fortemente tipizzato, perché il compilatore ha funzionato tutto che fuori mentre si strappava la sintassi originale a parte.

OK, non è niente di simile alle cose che tu ed io abbiamo a che fare, ma poi non le gestiamo!


4
Non esiste un singolo compilatore C ++ che produrrà qualcosa anche in remoto come questo. Generalmente producono un vero codice CPU e anche l'assemblaggio non ha questo aspetto.
Salterio del

3
l'intento qui era di dimostrare la differenza nella struttura tra il codice scritto e l'output compilato. anche qui la maggior parte delle persone non sarebbe in grado di leggere il codice o l'assemblaggio effettivo della macchina
DHall

@MSalters Nitpicking clang emetterebbe "qualcosa del genere" se trattassi LLVM ISA come "qualcosa del genere" (è SSA non basato su stack). Dato che è possibile scrivere back-end JVM per LLVM e JVM ISA (AFAIK) è basato su stack clang-> llvm-> JVM sarebbe molto simile.
Maciej Piechotka,

Non credo che LLVM ISA abbia la capacità di definire i nomi delle variabili dello stack usando valori letterali delle stringhe di runtime (solo le prime due istruzioni). Questo sta mescolando seriamente il runtime e il tempo di compilazione. La distinzione conta perché questa domanda riguarda esattamente quella confusione.
Salterio del

6

Dipende se è in virgola mobile o meno:

  • Nell'aggiunta aritmetica in virgola mobile non è associativa quindi l'ottimizzatore non può riordinare le operazioni (a meno che non si aggiunga l'opzione del compilatore fastmath).

  • Nelle operazioni con numeri interi possono essere riordinati.

Nel tuo esempio entrambi verranno eseguiti esattamente nello stesso momento perché verranno compilati con lo stesso codice esatto (l'aggiunta viene valutata da sinistra a destra).

tuttavia anche Java e C # saranno in grado di ottimizzarlo, lo faranno solo in fase di esecuzione.


+1 per aver indicato che le operazioni in virgola mobile non sono associative.
Doval,

Nell'esempio della domanda le parentesi non alterano l'associatività predefinita (a sinistra), quindi questo punto è controverso.
Marc van Leeuwen,

1
Per quanto riguarda l'ultima frase, non la penso così. In java a e c # il compilatore produrrà bytecode / IL ottimizzato. Il runtime non è interessato.
Stefano Altieri,

IL non funziona su espressioni di questo tipo, le istruzioni prendono un certo numero di valori da uno stack e restituiscono un certo numero di valori (generalmente 0 o 1) allo stack. Parlare di questo genere di cose che viene ottimizzato in fase di esecuzione in C # non ha senso.
Jon Hanna,

6

Il tipico compilatore C ++ si traduce in codice macchina, non in C ++ stesso . Rimuove le parentesi inutili, sì, perché quando è fatto, non ci sono affatto parentesi. Il codice macchina non funziona in questo modo.



1

No, ma sì, ma forse, ma forse viceversa, ma no.

Come le persone hanno già sottolineato, (supponendo un linguaggio in cui l'addizione è associativa di sinistra, come C, C ++, C # o Java) l'espressione ((1 + 2) + 3)è esattamente equivalente a1 + 2 + 3 . Sono modi diversi di scrivere qualcosa nel codice sorgente, che non avrebbero alcun effetto sul codice macchina o sul codice byte risultante.

In entrambi i casi il risultato sarà un'istruzione per esempio per aggiungere due registri e quindi aggiungere un terzo, o prendere due valori da uno stack, aggiungerlo, spingerlo indietro, quindi prenderlo e un altro e aggiungerli o aggiungere tre registri in una singola operazione o un altro modo per sommare tre numeri a seconda di ciò che è più sensato al livello successivo (il codice macchina o il codice byte). Nel caso del codice byte, ciò a sua volta subirà probabilmente una simile ristrutturazione in quella, ad esempio, l'equivalente IL di questo (che sarebbe una serie di carichi in uno stack e le coppie di pop-up da aggiungere e quindi respingere il risultato) non comporterebbe una copia diretta di tale logica a livello di codice macchina, ma qualcosa di più sensato per la macchina in questione.

Ma c'è qualcosa in più alla tua domanda.

Nel caso di qualsiasi compilatore C, C ++, Java o C # sano, mi aspetterei che il risultato di entrambe le istruzioni fornite fornisca esattamente gli stessi risultati di:

int a = 6;

Perché il codice risultante dovrebbe perdere tempo a fare matematica sui valori letterali? Nessuna modifica allo stato del programma fermerà il risultato 1 + 2 + 3dell'essere 6, quindi è quello che dovrebbe andare nel codice in esecuzione. Anzi, forse nemmeno quello (a seconda di cosa fai con quel 6, forse possiamo buttare via tutto; e anche C # con la sua filosofia di "non ottimizzare pesantemente, poiché il jitter ottimizzerà comunque questo" produrrà l'equivalente di int a = 6o semplicemente buttare via tutto come non necessario).

Questo però ci porta a una possibile estensione della tua domanda. Considera quanto segue:

int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;

e

int c;
if(d < 100)
  c = 0;
else
  c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);

(Nota, questi ultimi due esempi non sono C # validi, mentre tutto il resto qui è e sono validi in C, C ++ e Java.)

Anche in questo caso abbiamo esattamente un codice equivalente in termini di output. Poiché non sono espressioni costanti, non verranno calcolate al momento della compilazione. È possibile che una forma sia più veloce di un'altra. Qual è più veloce? Ciò dipenderebbe dal processore e forse da alcune differenze piuttosto arbitrarie nello stato (soprattutto perché se uno è più veloce, non è probabile che sia molto più veloce).

E non sono del tutto estranei alla tua domanda, in quanto riguardano principalmente le differenze nell'ordine in cui qualcosa viene concettualmente fatto.

In ciascuno di essi, c'è un motivo per sospettare che uno potrebbe essere più veloce dell'altro. I singoli decrementi possono avere un'istruzione specializzata, quindi (b / 2)--potrebbero essere più veloci di (b - 2) / 2. d * 32potrebbe forse essere prodotto più velocemente trasformandolo in d << 5così da renderlo d * 32 - dpiù veloce di d * 31. Le differenze tra gli ultimi due sono particolarmente interessanti; uno consente in alcuni casi di saltare alcune elaborazioni, ma l'altro evita la possibilità di errori di previsione del ramo.

Quindi, questo ci lascia con due domande: 1. L'uno è effettivamente più veloce dell'altro? 2. Un compilatore converte il più lento nel più veloce?

E la risposta è 1. Dipende. 2. Forse.

O per espandersi, dipende perché dipende dal processore in questione. Certamente ci sono stati processori in cui l'equivalente in codice macchina ingenuo di uno sarebbe più veloce dell'equivalente in codice macchina ingenuo dell'altro. Nel corso della storia dell'informatica elettronica, non ce n'è mai stato uno che fosse sempre il più veloce (l'elemento di previsione errata del ramo in particolare non era rilevante per molti quando le CPU senza pipeline erano più comuni).

E forse, poiché ci sono un sacco di diverse ottimizzazioni che compileranno (e nervosismo e motori di script), e mentre alcuni possono essere obbligati in alcuni casi, in genere saremo in grado di trovare alcuni pezzi di codice logicamente equivalente che anche il compilatore più ingenuo ha esattamente gli stessi risultati e alcuni pezzi di codice logicamente equivalente in cui anche il più sofisticato produce un codice più veloce per l'uno che per l'altro (anche se dobbiamo scrivere qualcosa di totalmente patologico solo per dimostrare il nostro punto).

Potrebbe sembrare una minuscola preoccupazione per l'ottimizzazione,

No. Anche con differenze più complicate di quelle che do qui, sembra una preoccupazione assolutamente minima che non ha nulla a che fare con l'ottimizzazione. Semmai, è una questione di pessimizzazione poiché sospetti che la lettura più difficile ((1 + 2) + 3potrebbe essere più lenta di quella più facile da leggere 1 + 2 + 3.

ma la scelta di C ++ su C # / Java / ... significa ottimizzazioni (IMHO).

Se questo è davvero ciò che scegliere C ++ su C # o Java era "tutto" direi che le persone dovrebbero masterizzare la loro copia di Stroustrup e ISO / IEC 14882 e liberare lo spazio del loro compilatore C ++ per lasciare spazio ad altri MP3 o qualcosa del genere.

Queste lingue hanno diversi vantaggi l'una sull'altra.

Uno di questi è che C ++ è ancora generalmente più veloce e leggero nell'uso della memoria. Sì, ci sono esempi in cui C # e / o Java sono più veloci e / o hanno un migliore utilizzo della memoria per tutta la durata dell'applicazione e questi stanno diventando più comuni man mano che le tecnologie coinvolte migliorano, ma possiamo ancora aspettarci che il programma medio scritto in C ++ sia un eseguibile più piccolo che fa il suo lavoro più velocemente e utilizza meno memoria dell'equivalente in una di queste due lingue.

Questa non è ottimizzazione.

L'ottimizzazione è talvolta usata per significare "rendere le cose più veloci". È comprensibile, perché spesso quando parliamo davvero di "ottimizzazione", stiamo effettivamente parlando di far andare le cose più velocemente, e quindi uno è diventato una scorciatoia per l'altro e ammetterò di abusare della parola in quel modo.

La parola corretta per "rendere le cose più veloci" non è ottimizzazione . La parola corretta qui è miglioramento . Se apporti una modifica a un programma e l'unica differenza significativa è che ora è più veloce, non è ottimizzato in alcun modo, è solo meglio.

L'ottimizzazione è quando apportiamo un miglioramento per quanto riguarda un aspetto e / o un caso particolari. Esempi comuni sono:

  1. Ora è più veloce per un caso d'uso, ma più lento per un altro.
  2. Ora è più veloce, ma utilizza più memoria.
  3. Ora è più leggero in memoria, ma più lento.
  4. Ora è più veloce, ma più difficile da mantenere.
  5. Ora è più facile da mantenere, ma più lento.

Tali casi sarebbero giustificati se, ad esempio:

  1. Il caso d'uso più veloce è più comune o più gravemente ostacolato all'inizio.
  2. Il programma era inaccettabilmente lento e abbiamo molta RAM libera.
  3. Il programma si fermava perché utilizzava così tanta RAM da passare più tempo a scambiarsi che a eseguire la sua elaborazione superveloce.
  4. Il programma era inaccettabilmente lento e il codice più difficile da capire è ben documentato e relativamente stabile.
  5. Il programma è ancora accettabilmente veloce e la base di codice più comprensibile è più economica da mantenere e consente di apportare più facilmente altri miglioramenti.

Ma tali casi non sarebbero giustificati anche in altri scenari: il codice non è stato reso migliore da una misura assoluta infallibile di qualità, è stato reso migliore sotto un aspetto particolare che lo rende più adatto per un uso particolare; ottimizzato.

E la scelta della lingua ha un effetto qui, perché la velocità, l'uso della memoria e la leggibilità possono essere influenzati da essa, ma anche la compatibilità con altri sistemi, la disponibilità di librerie, la disponibilità di runtime, la maturità di quei runtime su un determinato sistema operativo (per i miei peccati ho in qualche modo finito con avere Linux e Android come i miei sistemi operativi preferiti e C # come la mia lingua preferita, e mentre Mono è fantastico, ma mi trovo ancora un po 'contro questo).

Dire "scegliere C ++ su C # / Java / ... significa ottimizzazioni" ha senso solo se pensi che C ++ faccia davvero schifo, perché l'ottimizzazione riguarda "meglio nonostante ..." non "meglio". Se pensi che C ++ sia migliore nonostante se stesso, l'ultima cosa di cui hai bisogno è preoccuparti di micro-opts così minuscoli. In effetti, probabilmente stai meglio abbandonandolo affatto; gli hacker felici sono anche una qualità da ottimizzare!

Se, tuttavia, sei propenso a dire "Adoro il C ++ e una delle cose che adoro è spremere cicli extra", allora è una questione diversa. È comunque un caso che le micro-opte valgano la pena solo se possono essere un'abitudine riflessiva (vale a dire, il modo in cui tendi a codificare naturalmente sarà più veloce di quanto non sia più lento). Altrimenti non sono nemmeno ottimizzazioni premature, sono pessimizzazioni premature che peggiorano le cose.


0

Le parentesi sono lì per dire al compilatore in quale ordine devono essere valutate le espressioni. A volte sono inutili (tranne per il fatto che migliorano o peggiorano la leggibilità), perché specificano l'ordine che sarebbe comunque utilizzato. A volte cambiano l'ordine. Nel

int a = 1 + 2 + 3;

praticamente ogni lingua esistente ha una regola secondo cui la somma viene valutata aggiungendo 1 + 2, quindi aggiungendo il risultato più 3. Se hai scritto

int a = 1 + (2 + 3);

quindi la parentesi forzerebbe un ordine diverso: prima aggiungendo 2 + 3, quindi aggiungendo 1 più il risultato. Il tuo esempio di parentesi produce lo stesso ordine che sarebbe stato prodotto comunque. Ora in questo esempio, l'ordine delle operazioni è leggermente diverso, ma il modo in cui funziona l'aggiunta di numeri interi, il risultato è lo stesso. Nel

int a = 10 - (5 - 4);

le parentesi sono fondamentali; lasciarli fuori cambierebbe il risultato da 9 a 1.

Dopo che il compilatore ha determinato quali operazioni vengono eseguite in quale ordine, la parentesi viene completamente dimenticata. Tutto ciò che il compilatore ricorda a questo punto è quali operazioni eseguire in quale ordine. Quindi non c'è nulla che il compilatore possa ottimizzare qui, le parentesi sono sparite .


practically every language in existence; tranne APL: prova (qui) [tryapl.org] inserendo (1-2)+3(2), 1-(2+3)(-4) e 1-2+3(anche -4).
tomsmeding

0

Sono d'accordo con gran parte di ciò che è stato detto, tuttavia ... l'over-arch qui è che le parentesi sono lì per forzare l'ordine delle operazioni ... cosa che il compilatore sta assolutamente facendo. Sì, produce codice macchina ... ma non è questo il punto e non è ciò che viene chiesto.

Le parentesi sono davvero sparite: come è stato detto, non fanno parte del codice macchina, che è numeri e non altro. Il codice di assemblaggio non è un codice macchina, è leggibile dall'uomo e contiene le istruzioni per nome, non per codice operativo. La macchina esegue i cosiddetti codici operativi, rappresentazioni numeriche del linguaggio assembly.

Linguaggi come Java cadono in una zona intermedia mentre si compilano solo parzialmente sulla macchina che li produce. Vengono compilati per codice specifico della macchina sulla macchina che li esegue, ma ciò non fa differenza per questa domanda: le parentesi sono ancora sparite dopo la prima compilazione.


1
Non sono sicuro che questo risponda alla domanda. I paragrafi di supporto sono più confusi che utili. In che modo il compilatore Java è rilevante per il compilatore C ++?
Adam Zuckerman,

OP ha chiesto se le parentesi fossero sparite ... Ho detto che lo erano e ho ulteriormente spiegato che il codice eseguibile è solo un numero che rappresenta i codici operativi. Java è stato cresciuto in un'altra risposta. Penso che risponda bene alla domanda ... ma questa è solo la mia opinione. Grazie per la risposta.
jinzai,

3
Le parentesi non impongono "l'ordine di funzionamento". Cambiano precedenza. Così, in a = f() + (g() + h());, il compilatore è libero di chiamare f, ge hin questo ordine (o in qualsiasi ordine che piace).
Alok,

Sono in disaccordo con questa affermazione ... puoi assolutamente forzare l'ordine delle operazioni tra parentesi.
jinzai,

0

I compilatori, indipendentemente dalla lingua, traducono tutta la matematica infografica in postfisso. In altre parole, quando il compilatore vede qualcosa di simile:

((a+b)+c)

lo traduce in questo:

 a b + c +

Questo viene fatto perché mentre la notazione infix è più facile da leggere per le persone, la notazione postfix è molto più vicina ai passi effettivi che il computer deve compiere per portare a termine il lavoro (e poiché esiste già un algoritmo ben sviluppato per esso). definizione, postfix ha eliminato tutti i problemi con l'ordine delle operazioni o le parentesi, il che rende le cose molto più semplici quando si scrive effettivamente il codice macchina.

Raccomando l'articolo di Wikipedia sulla notazione polacca inversa per ulteriori informazioni sull'argomento.


5
Questo è un presupposto errato su come i compilatori traducono le operazioni. Ad esempio, stai assumendo una macchina stack qui. E se avessi un processore vettoriale? e se avessi una macchina con una grande quantità di registri?
Ahmed Masud,
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.