2012-04-30 19 views
20

Quali sono i vantaggi o le penali delle prestazioni dell'uso di goto con un compilatore C++ moderno?di goto sull'ottimizzazione del compilatore C++

Sto scrivendo un generatore di codice C++ e l'utilizzo di goto renderà più semplice la scrittura. Nessuno toccherà i file C++ risultanti quindi non ottenere tutto "goto is bad" su di me. Come vantaggio, salvano l'uso di variabili temporanee.

Mi chiedevo, da una prospettiva di ottimizzazione puramente del compilatore, il risultato che goto ha sull'ottimizzatore del compilatore? Rende il codice più veloce, più lento, o in genere nessuna modifica in termini di prestazioni rispetto all'utilizzo di temporanei/flag.

+1

Ma ... lo sono! –

+1

@MrLister: in C o in C++? In C direi che 'goto' è necessario. In C++, non vedo alcun motivo per usarli ... abbiamo già delle eccezioni! –

+0

Macchine di stato senza temporaries. – unixman83

risposta

21

La parte di un compilatore interessata funziona con un diagramma di flusso. La sintassi che usi per creare un particolare diagramma di flusso sarà normalmente irrilevante - se crei qualcosa come un ciclo while usando un goto invece di una vera e propria while affermazione, non influenzerà affatto l'ottimizzatore (a quel punto, la sintassi quello che ha prodotto il diagramma di flusso sarà scomparso da tempo).

E 'possibile, tuttavia, produrre un grafico di flusso con goto s che non può essere prodotto da alcuna normale istruzione di controllo del flusso (loop, switch, ecc.) In tal caso, è possibile produrre un grafico di flusso irriducibile e quando/se lo fai, ciò limiterà spesso la capacità del compilatore di ottimizzare il codice.

In altre parole, se (ad esempio) si ha il codice che è stato scritto con il normale for, while, switch, ecc, e convertito in modo da utilizzare goto in ogni caso, ma ha mantenuto la stessa struttura, quasi tutti abbastanza moderno il compilatore probabilmente produrrebbe un codice essenzialmente identico in entrambi i casi. Se, tuttavia, usi goto s per produrre il casino di spaghetti come gran parte del FORTRAN che dovevo guardare decine di anni fa, il compilatore probabilmente non sarà in grado di fare molto con esso.

+2

La completezza di Turing non ci assicura che qualunque pasticcio possa essere creato con 'goto', possiamo creare un equivalente senza di esso? –

+1

@MatthieuM .: Suppongo che tu possa vederlo in termini di completezza di Turing, ma è stato studiato molto più specificamente di questo, e almeno IIRC, ci sono prove che gli elementi accettati della programmazione strutturata sono sufficienti per produrre qualsiasi flusso possibile di controllo (anche se a volte a scapito della duplicazione del codice o dell'introduzione di variabili extra). –

+0

@JerryCoffin La tua risposta sull'uso di 'gotos' nel secondo caso è molto vaga. Quindi il compilatore inserisce semplicemente un'istruzione 'JMP' raw? – unixman83

5

Come pensi che i loop siano rappresentati, a livello di assemblaggio?

Utilizzando salto istruzioni ai etichette ...

molti compilatori sarà effettivamente uso salti anche nella loro rappresentazione intermedia:

int loop(int* i) { 
    int result = 0; 
    while(*i) { 
    result += *i; 
    } 
    return result; 
} 

int jump(int* i) { 
    int result = 0; 
    while (true) { 
    if (not *i) { goto end; } 
    result += *i; 
    } 

end: 
    return result; 
} 

rendimenti in LLVM:

define i32 @_Z4loopPi(i32* nocapture %i) nounwind uwtable readonly { 
    %1 = load i32* %i, align 4, !tbaa !0 
    %2 = icmp eq i32 %1, 0 
    br i1 %2, label %3, label %.lr.ph..lr.ph.split_crit_edge 

.lr.ph..lr.ph.split_crit_edge:     ; preds = %.lr.ph..lr.ph.split_crit_edge, %0 
    br label %.lr.ph..lr.ph.split_crit_edge 

; <label>:3          ; preds = %0 
    ret i32 0 
} 

define i32 @_Z4jumpPi(i32* nocapture %i) nounwind uwtable readonly { 
    %1 = load i32* %i, align 4, !tbaa !0 
    %2 = icmp eq i32 %1, 0 
    br i1 %2, label %3, label %.lr.ph..lr.ph.split_crit_edge 

.lr.ph..lr.ph.split_crit_edge:     ; preds = %.lr.ph..lr.ph.split_crit_edge, %0 
    br label %.lr.ph..lr.ph.split_crit_edge 

; <label>:3          ; preds = %0 
    ret i32 0 
} 

Dove br è il branc h istruzione (un salto condizionale).

Tutte le ottimizzazioni vengono eseguite su questa struttura. Quindi, goto è il pane e il burro degli ottimizzatori.

+4

Credo che la domanda sia se usare goto impedisce al compilatore di dedurre alcune ottimizzazioni. –

+1

@OliCharlesworth: ne dubito. Il pericolo con 'goto' è il problema sugli ambiti (specialmente saltare in un ambito). Ho aggiunto la rappresentazione intermedia di LLVM (suppongo che gcc's sia abbastanza simile) che è composta da blocchi collegati da salti ... e che è * la * rappresentazione su cui vengono eseguite le ottimizzazioni. –

+1

@Matthieu E ci sono chiaramente possibili flussi di controllo che non possono essere riprodotti con costrutti strutturati, quindi il gotos può avere un effetto lì. Un esempio a cui posso pensare è che la semplice generazione SSA a passaggio singolo non funziona con goto arbitrario - mentre è possibile ottenere lo stesso effetto è molto più coinvolto. – Voo

6

Mi chiedevo, da un punto di vista di ottimizzazione puramente compilatore, il risultato che goto ha sull'ottimizzatore del compilatore? Rende il codice più veloce, più lento o in generale nessun cambiamento nelle prestazioni rispetto all'uso di temporaries/flag.

Perché te ne importa? La tua preoccupazione principale dovrebbe essere ottenere il generatore di codice per creare il codice corretto. L'efficienza è molto meno importante della correttezza. La tua domanda dovrebbe essere "Il mio uso del gotos renderà il mio codice generato più probabile o meno probabile che sia corretto?"

Guarda il codice generato da lex/yacc o flex/bison. Quel codice è pieno zeppo di gotos. C'è una buona ragione per quello. lex e yacc implementano macchine a stati finiti. Poiché la macchina passa a un altro stato nelle transizioni di stato, il goto è probabilmente lo strumento più naturale per tali transizioni.

C'è un modo semplice per eliminare quei goto in molti casi utilizzando un ciclo while attorno a un'istruzione switch. Questo è un codice strutturato. Per Douglas Jones (Jones D.W., Come (non) per codificare una macchina a stati finiti, SIGPLAN Not. 23, 8 (agosto 1988), 19-22.), Questo è il modo peggiore per codificare un FSM. Sostiene che uno schema goto-based è migliore.

Sostiene inoltre che esiste un approccio ancora migliore, che converte il tuo FSM in un diagramma di flusso di controllo usando tecniche di teoria dei grafi. Non è sempre facile. È un problema difficile per NP. Ecco perché vedi ancora molti FSM, in particolare FSM auto-generati, implementati come loop su uno switch o con transizioni di stato implementate via gotos.

+1

* "Perché ti interessa? La tua preoccupazione principale dovrebbe essere ottenere il generatore di codice per creare il codice corretto." * Ora questa è la mentalità giusta che rispetto. – SigTerm

+2

È certamente la preoccupazione principale. Tuttavia, la decisione di evitare 'goto' può essere vista anche come una pessimizzazione prematura. Che tu li usi o meno è una decisione in entrambi i casi, e come tutte le decisioni dovrebbero essere prese considerando le prove a portata di mano. La decisione convenzionale di evitarli si basa su prove che non si applicano qui (manutenibilità) e quindi sono necessarie altre ragioni. – MSalters

3

Sono d'accordo con la risposta di David Hammen, ma ho solo un punto da aggiungere.

Quando le persone vengono istruite sui compilatori, vengono insegnate tutte le meravigliose ottimizzazioni che i compilatori possono fare.

Non viene insegnato che il valore effettivo di questo dipende da chi è l'utente.

Se il codice che si sta scrivendo (o si genera) e si compila contiene pochissime chiamate di funzione e potrebbe consumare esso stesso una grande parte del tempo di altri programmi, allora sì, l'ottimizzazione del compilatore è importante.

Se il codice generato contiene chiamate di funzione, o se per qualche altro motivo il contatore del programma trascorre una piccola parte del suo tempo nel codice generato, non vale la pena preoccuparsi. Perché? Perché anche se quel codice potesse essere ottimizzato in modo così aggressivo da richiedere tempo pari a zero, non si risparmia più di quella piccola frazione, e probabilmente ci sono problemi di prestazioni molto più grandi, che il compilatore non può correggere, che sono felici di essere sfuggire alla tua attenzione.

Problemi correlati