Le eccezioni in C ++ sono davvero lente


98

Stavo guardando Systematic Error Handling in C ++ - Andrei Alexandrescu afferma che le eccezioni in C ++ sono molto lente.

È ancora vero per C ++ 98?


42
Non ha senso chiedere se le "eccezioni C ++ 98" sono più veloci / lente delle "eccezioni C ++ 03" o delle "eccezioni C ++ 11". Le loro prestazioni dipendono da come il compilatore le implementa nei programmi e lo standard C ++ non dice nulla su come dovrebbero essere implementate; l'unico requisito è che il loro comportamento debba seguire lo standard (la regola del "come se").
In silico

Domanda correlata (ma non duplicata): stackoverflow.com/questions/691168/…
Philipp

2
sì, è molto lento, ma non dovrebbero essere lanciati per una normale operazione o usati come ramo
BЈовић

Ho trovato una domanda simile .
PaperBirdMaster

Per chiarire ciò che ha detto BЈовић, l'uso delle eccezioni non è qualcosa di cui aver paura. È quando viene generata un'eccezione che si verificano (potenzialmente) operazioni che richiedono tempo. Sono anche curioso di sapere perché vuoi sapere per C ++ 89 in particolare ... l'ultima versione è C ++ 11, e il tempo necessario per l'esecuzione delle eccezioni è definito dall'implementazione, quindi il mio 'potenzialmente' dispendio di tempo .
thecoshman

Risposte:


162

Il modello principale utilizzato oggi per le eccezioni (Itanium ABI, VC ++ 64 bit) sono le eccezioni del modello a costo zero.

L'idea è che invece di perdere tempo impostando una guardia e controllando esplicitamente la presenza di eccezioni ovunque, il compilatore generi una tabella laterale che mappa qualsiasi punto che può lanciare un'eccezione (contatore del programma) a un elenco di gestori. Quando viene generata un'eccezione, questo elenco viene consultato per scegliere il gestore destro (se presente) e lo stack viene svolto.

Rispetto alla if (error)strategia tipica :

  • il modello a costo zero, come suggerisce il nome, è gratuito quando non si verificano eccezioni
  • costa circa 10x / 20x ifquando si verifica un'eccezione

Il costo però non è banale da misurare:

  • Il tavolino è generalmente freddo , quindi recuperarlo dalla memoria richiede molto tempo
  • Determinare il gestore destro implica RTTI: molti descrittori RTTI da recuperare, sparsi nella memoria e operazioni complesse da eseguire (fondamentalmente un dynamic_casttest per ogni gestore)

Quindi, per lo più la cache manca, e quindi non è banale rispetto al puro codice della CPU.

Nota: per maggiori dettagli, leggere il report TR18015, capitolo 5.4 Gestione delle eccezioni (pdf)

Quindi, sì, le eccezioni sono lente sul percorso eccezionale , ma sono altrimenti più veloci dei controlli espliciti ( ifstrategia) in generale.

Nota: Andrei Alexandrescu sembra mettere in dubbio questo "più veloce". Personalmente ho visto le cose oscillare in entrambi i modi, alcuni programmi sono più veloci con le eccezioni e altri sono più veloci con i rami, quindi sembra esserci una perdita di ottimizzazione in determinate condizioni.


Importa ?

Direi di no. Un programma dovrebbe essere scritto pensando alla leggibilità , non alle prestazioni (almeno, non come primo criterio). Le eccezioni devono essere utilizzate quando ci si aspetta che il chiamante non possa o non vorrà gestire il fallimento sul posto e passarlo allo stack. Bonus: in C ++ 11 è possibile eseguire il marshalling delle eccezioni tra i thread utilizzando la libreria standard.

Questo è sottile però, sostengo che map::findnon dovrebbe lanciare ma mi va bene map::findrestituire un checked_ptrche lancia se un tentativo di dereferenziare fallisce perché è nullo: in quest'ultimo caso, come nel caso della classe introdotta da Alexandrescu, il chiamante sceglie tra controllo esplicito e fare affidamento sulle eccezioni. Dare potere al chiamante senza dargli maggiori responsabilità di solito è un segno di buon design.


3
+1 Vorrei solo aggiungere quattro cose: (0) sul supporto per il rilancio aggiunto in C ++ 11; (1) un riferimento alla relazione della commissione sull'efficienza del c ++; (2) alcune osservazioni sulla correttezza (come superare anche la leggibilità); e (3) sulla performance, commenti su come misurarla rispetto al caso in cui non si utilizzino eccezioni (tutto è relativo)
Cheers e hth. - Alf

2
@ Cheersandhth.-Alf: (0), (1) e (3) fatto: grazie. Per quanto riguarda la correttezza (2), sebbene trionfi sulla leggibilità, non sono sicuro delle eccezioni che portano a un codice più corretto rispetto ad altre strategie di gestione degli errori (è così facile dimenticare i molti percorsi invisibili di eccezioni di esecuzione creati).
Matthieu M.

2
La descrizione può essere corretta localmente, ma può valere la pena notare che la presenza di eccezioni ha implicazioni globali sulle ipotesi e le ottimizzazioni che il compilatore può fare. Queste implicazioni soffrono del problema di non avere "banali controesempi", poiché il compilatore può sempre vedere attraverso un piccolo programma. La creazione di profili su una base di codice ampia e realistica con e senza eccezioni può essere una buona idea.
Kerrek SB

4
> il modello a costo zero, come suggerisce il nome, è gratuito quando non si verificano eccezioni, ciò non è vero fino ai minimi livelli di dettaglio. la generazione di più codice ha sempre un impatto sulle prestazioni, anche se piccola e impercettibile ... potrebbe volerci un po 'più di tempo prima che il sistema operativo carichi l'eseguibile, o avrai più errori di i-cache. inoltre, che dire del codice di svolgimento dello stack? inoltre, che dire degli esperimenti che puoi fare per misurare gli effetti invece di cercare di capirlo con il pensiero razionale?
jheriko

2
@jheriko: credo di aver già risposto alla maggior parte delle tue domande, in realtà. Il tempo di caricamento non dovrebbe essere influenzato (il codice freddo non dovrebbe essere caricato), l'i-cache non dovrebbe essere influenzato (il codice freddo non dovrebbe entrare nell'i-cache), ... quindi per affrontare l'unica domanda mancante: "how to measure" => sostituire qualsiasi eccezione lanciata con una chiamata a abortti permetterà di misurare l'impronta di dimensione binaria e di controllare che il tempo di caricamento / i-cache si comporti in modo simile. Ovviamente, meglio non colpire nessuno dei abort...
Matthieu M.

60

Quando la domanda è stata posta stavo andando dal dottore, con un taxi in attesa, quindi ho avuto solo il tempo per un breve commento. Ma avendo ora commentato e votato verso l'alto e verso il basso, farei meglio ad aggiungere la mia risposta. Anche se la risposta di Matthieu è già abbastanza buona.


Le eccezioni sono particolarmente lente in C ++, rispetto ad altri linguaggi?

Re il reclamo

"Stavo guardando Systematic Error Handling in C ++ - Andrei Alexandrescu afferma che le eccezioni in C ++ sono molto lente."

Se questo è letteralmente ciò che afferma Andrei, allora per una volta è molto fuorviante, se non addirittura sbagliato. Per le eccezioni sollevate / lanciate è sempre lento rispetto ad altre operazioni di base nel linguaggio, indipendentemente dal linguaggio di programmazione . Non solo in C ++ o più in C ++ che in altri linguaggi, come indica l'affermazione presunta.

In generale, per lo più indipendentemente dalla lingua, le due caratteristiche del linguaggio di base che sono ordini di grandezza più lente delle altre, perché si traducono in chiamate di routine che gestiscono strutture di dati complesse, sono

  • lancio di eccezioni e

  • allocazione dinamica della memoria.

Fortunatamente in C ++ si possono spesso evitare entrambi nel codice time-critical.

Sfortunatamente non esiste un pranzo gratuito , anche se l'efficienza predefinita del C ++ si avvicina molto. :-) L'efficienza ottenuta evitando il lancio di eccezioni e l'allocazione dinamica della memoria è generalmente ottenuta codificando a un livello inferiore di astrazione, usando C ++ solo come un "C migliore". E una minore astrazione significa maggiore "complessità".

Una maggiore complessità significa più tempo speso per la manutenzione e poco o nessun vantaggio dal riutilizzo del codice, che sono costi monetari reali, anche se difficili da stimare o misurare. Cioè, con C ++ si può, se lo si desidera, scambiare un po 'di efficienza del programmatore con l'efficienza di esecuzione. Se farlo è in gran parte una decisione ingegneristica e intuitiva, perché in pratica solo il guadagno, non il costo, può essere facilmente stimato e misurato.


Esistono misure oggettive delle prestazioni che generano eccezioni C ++?

Sì, il comitato internazionale di standardizzazione C ++ ha pubblicato un rapporto tecnico sulle prestazioni C ++, TR18015 .


Cosa significa che le eccezioni sono "lente"?

Principalmente significa che un throwpuò impiegare molto tempo ™ rispetto ad esempio a un intincarico, a causa della ricerca di un conduttore.

Come TR18015 discute nella sua sezione 5.4 "Eccezioni", ci sono due principali strategie di implementazione della gestione delle eccezioni,

  • l'approccio in cui ogni tryblocco imposta dinamicamente il rilevamento delle eccezioni, in modo che una ricerca lungo la catena dinamica dei gestori venga eseguita quando viene generata un'eccezione, e

  • l'approccio in cui il compilatore genera tabelle di ricerca statiche utilizzate per determinare il gestore per un'eccezione generata.

Il primo approccio molto flessibile e generale è quasi forzato in Windows a 32 bit, mentre in land a 64 bit e in * nix-land viene comunemente utilizzato il secondo approccio molto più efficiente.

Inoltre, come discusso nel rapporto, per ogni approccio ci sono tre aree principali in cui la gestione delle eccezioni ha un impatto sull'efficienza:

  • try-blocchi,

  • funzioni regolari (opportunità di ottimizzazione) e

  • throw-espressioni.

Principalmente, con l'approccio del gestore dinamico (Windows a 32 bit) la gestione delle eccezioni ha un impatto sui tryblocchi, principalmente indipendentemente dalla lingua (perché questo è forzato dallo schema di gestione delle eccezioni strutturato di Windows ), mentre l'approccio della tabella statica ha un costo approssimativamente zero per try: blocchi. Discutere di questo richiederebbe molto più spazio e ricerca di quanto sia pratico per una risposta SO. Quindi, vedere il rapporto per i dettagli.

Sfortunatamente il rapporto, del 2006, è già un po 'datato alla fine del 2012 e per quanto ne so non c'è niente di paragonabile che sia più recente.

Un'altra prospettiva importante è che l'impatto dell'uso delle eccezioni sulle prestazioni è molto diverso dall'efficienza isolata delle funzionalità linguistiche di supporto, perché, come osserva il rapporto,

"Quando si considera la gestione delle eccezioni, è necessario confrontarla con metodi alternativi di gestione degli errori."

Per esempio:

  • Costi di manutenzione dovuti a diversi stili di programmazione (correttezza)

  • ifControllo degli errori del sito di chiamata ridondante rispetto a quello centralizzatotry

  • Problemi di memorizzazione nella cache (ad esempio, un codice più breve potrebbe entrare nella cache)

Il report ha un diverso elenco di aspetti da considerare, ma comunque l'unico modo pratico per ottenere dati concreti sull'efficienza di esecuzione è probabilmente quello di implementare lo stesso programma usando eccezioni e non usando eccezioni, entro un limite stabilito sui tempi di sviluppo e con gli sviluppatori familiarità con ogni modo, quindi MISURA .


Qual è un buon modo per evitare il sovraccarico delle eccezioni?

La correttezza trionfa quasi sempre sull'efficienza.

Senza eccezioni, può facilmente accadere quanto segue:

  1. Alcuni codici P hanno lo scopo di ottenere una risorsa o calcolare alcune informazioni.

  2. Il codice chiamante C dovrebbe aver controllato per successo / fallimento, ma non lo fa.

  3. Una risorsa inesistente o informazioni non valide vengono utilizzate nel codice che segue C, causando un caos generale.

Il problema principale è il punto (2), dove con il solito schema del codice di ritorno il codice chiamante C non è obbligato a controllare.

Esistono due approcci principali che impongono tale controllo:

  • Dove P genera direttamente un'eccezione quando fallisce.

  • Dove P restituisce un oggetto che C deve ispezionare prima di utilizzare il suo valore principale (altrimenti un'eccezione o una terminazione).

Il secondo approccio è stato, per quanto ne so, descritto per la prima volta da Barton e Nackman nel loro libro * Scientific and Engineering C ++: An Introduction with Advanced Techniques and examples , dove hanno introdotto una classe chiamata Fallowper un risultato di funzione "possibile". Una classe simile chiamata optionalè ora offerta dalla libreria Boost. E puoi facilmente implementare una Optionalclasse da solo, utilizzando un std::vectorvettore come valore per il caso di risultato non POD.

Con il primo approccio, il codice chiamante C non ha altra scelta che utilizzare tecniche di gestione delle eccezioni. Con il secondo approccio, tuttavia, il codice chiamante C può decidere se eseguire il ifcontrollo basato o la gestione delle eccezioni generali. Pertanto, il secondo approccio supporta il compromesso tra efficienza del programmatore e tempo di esecuzione.


Qual è l'impatto dei vari standard C ++ sulle prestazioni delle eccezioni?

"Voglio sapere se questo è ancora vero per C ++ 98"

C ++ 98 è stato il primo standard C ++. Per le eccezioni ha introdotto una gerarchia standard di classi di eccezioni (purtroppo piuttosto imperfetta). L'impatto principale sulle prestazioni è stata la possibilità di specifiche di eccezione (rimosse in C ++ 11), che tuttavia non sono mai state completamente implementate dal compilatore principale di Windows C ++ Visual C ++: Visual C ++ accetta la sintassi della specifica di eccezione C ++ 98, ma ignora semplicemente specifiche di eccezione.

C ++ 03 era solo una correzione tecnica di C ++ 98. L'unica novità in C ++ 03 è stata l' inizializzazione del valore . Che non ha nulla a che fare con le eccezioni.

Con lo standard C ++ 11 le specifiche delle eccezioni generali sono state rimosse e sostituite con la noexceptparola chiave.

Lo standard C ++ 11 ha anche aggiunto il supporto per l'archiviazione e il rilancio delle eccezioni, che è ottimo per propagare le eccezioni C ++ tra i callback del linguaggio C. Questo supporto limita in modo efficace la modalità di archiviazione dell'eccezione corrente. Tuttavia, per quanto ne so, ciò non influisce sulle prestazioni, tranne nella misura in cui nel nuovo codice la gestione delle eccezioni può essere più facilmente utilizzata su entrambi i lati di un callback in linguaggio C.


6
"le eccezioni sono sempre lente rispetto ad altre operazioni di base nel linguaggio, indipendentemente dal linguaggio di programmazione" ... tranne nei linguaggi progettati per compilare l'uso delle eccezioni nel controllo di flusso ordinario.
Ben Voigt

4
"lanciare un'eccezione implica sia l'allocazione che lo svolgimento dello stack". Anche questo ovviamente non è vero in generale e, ancora una volta, OCaml è un contro esempio. Nei linguaggi Garbage Collection non è necessario srotolare lo stack perché non ci sono distruttori, quindi devi solo longjmpandare al gestore.
JD

2
@ JonHarrop: presumibilmente non sei a conoscenza del fatto che Pyhon abbia una clausola finale per la gestione delle eccezioni. questo significa che un'implementazione Python ha lo svolgimento dello stack o non è Python. sembri essere completamente ignorante degli argomenti su cui fai affermazioni (fantasy). spiacente.
Saluti e salute. - Alf

2
@ Cheersandhth.-Alf: "Pyhon ha una clausola infine per la gestione delle eccezioni. Ciò significa che un'implementazione di Python ha lo svolgimento dello stack o non è Python". Il try..finallycostrutto può essere implementato senza lo svolgimento dello stack. F #, C # e Java vengono implementati try..finallysenza utilizzare lo svolgimento dello stack. Devi solo longjmpal conduttore (come ho già spiegato).
JD

4
@ JonHarrop: sembri porre un dilemma. ma non ha rilevanza per nulla che sia stato discusso finora, e finora hai pubblicato una lunga sequenza di sciocchezze dal suono negativo . Dovrei fidarmi di te per essere d'accordo o meno con una formulazione vaga, perché come antagonista stai scegliendo ciò che rivelerai che "significa", e certamente non mi fido di te dopo tutte quelle sciocchezze senza senso, downvoting ecc.
Saluti e salute. - Alf

13

Non puoi mai rivendicare le prestazioni a meno che non converti il ​​codice nell'assembly o lo benchmark.

Ecco cosa vedi: (panchina veloce)

Il codice di errore non è sensibile alla percentuale di occorrenza. Le eccezioni hanno un po 'di sovraccarico fintanto che non vengono mai lanciate. Una volta che li lanci, inizia la miseria. In questo esempio, viene lanciato per lo 0%, 1%, 10%, 50% e 90% dei casi. Quando le eccezioni vengono lanciate il 90% delle volte, il codice è 8 volte più lento rispetto al caso in cui le eccezioni vengono lanciate il 10% delle volte. Come vedi, le eccezioni sono davvero lente. Non usarli se vengono lanciati frequentemente. Se la tua applicazione non ha requisiti in tempo reale, sentiti libero di lanciarli se si verificano molto raramente.

Vedi molte opinioni contraddittorie su di loro. Ma infine, le eccezioni sono lente? Non giudico. Guarda il benchmark.

Benchmark delle prestazioni delle eccezioni C ++


12

Dipende dal compilatore.

GCC, ad esempio, era noto per avere prestazioni molto scarse durante la gestione delle eccezioni, ma negli ultimi anni è migliorato notevolmente.

Ma si noti che la gestione delle eccezioni dovrebbe, come dice il nome, essere l'eccezione piuttosto che la regola nella progettazione del software. Quando si dispone di un'applicazione che genera così tante eccezioni al secondo da influire sulle prestazioni e questo è ancora considerato un funzionamento normale, è preferibile pensare a fare le cose in modo diverso.

Le eccezioni sono un ottimo modo per rendere il codice più leggibile eliminando tutto quel goffo codice di gestione degli errori, ma non appena diventano parte del normale flusso del programma, diventano davvero difficili da seguire. Ricorda che a throwè praticamente un goto catchtravestito.


-1 re la domanda così com'è ora, "è ancora vero per C ++ 98", che certamente non dipende dal compilatore. inoltre, questa risposta throw new Exceptionè un Java-ismo. di regola non si dovrebbero mai lanciare indicatori.
Saluti e salute. - Alf

1
lo standard 98 detta esattamente come devono essere implementate le eccezioni?
thecoshman

6
C ++ 98 è uno standard ISO, non un compilatore. Ci sono molti compilatori che lo implementano.
Philipp

3
@thecoshman: No. Lo standard C ++ non dice nulla su come dovrebbe essere implementato qualcosa (con la possibile eccezione della parte "Limiti di implementazione" dello standard).
In silico

2
@Insilico quindi posso solo trarre la conclusione logica che (sorprendentemente) è l'implementazione definita (lettura, specifica del compilatore) come si comportano le eccezioni.
thecoshman

4

Sì, ma non importa. Perché?
Leggi questo:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx

Fondamentalmente questo dice che l'uso di eccezioni come descritto da Alexandrescu (rallentamento 50x perché usano catchas else) è semplicemente sbagliato. Detto questo per ppl a cui piace farlo in quel modo, vorrei che C ++ 22 :) aggiungesse qualcosa del tipo:
(nota che questo dovrebbe essere un linguaggio di base poiché è fondamentalmente il compilatore che genera codice da uno esistente)

result = attempt<lexical_cast<int>>("12345");  //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...     
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
     int x = result.get(); // or result.result;
}
else 
{
     // even possible to see what is the exception that would have happened in original function
     switch (result.exception_type())
     //...

}

PS nota anche che anche se le eccezioni sono così lente ... non è un problema se non passi molto tempo in quella parte del codice durante l'esecuzione ... Ad esempio se la divisione in virgola mobile è lenta e la fai 4x più veloce che non importa se passi lo 0,3% del tuo tempo a fare la divisione FP ...


0

Come in silico, la sua implementazione dipende dall'implementazione, ma in generale le eccezioni sono considerate lente per qualsiasi implementazione e non dovrebbero essere utilizzate in codice ad alta intensità di prestazioni.

EDIT: Non sto dicendo di non usarli affatto, ma per il codice ad alte prestazioni è meglio evitarli.


9
Questo è nella migliore delle ipotesi un modo molto semplicistico di considerare le prestazioni eccezionali. Ad esempio, GCC utilizza un'implementazione "a costo zero" in cui non si incorre in un calo delle prestazioni se non vengono generate eccezioni. E le eccezioni sono pensate per circostanze eccezionali (cioè rare), quindi anche se sono lente per qualche metrica, non è ancora un motivo sufficiente per non usarle.
In silico

@insilico se guardi perché ho detto, non ho detto di non usare le eccezioni punto e basta. Ho specificato un codice ad alta intensità di prestazioni, questa è una valutazione accurata, principalmente lavoro con gpgpus e verrei colpito se usassi delle eccezioni.
Chris McCabe
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.