Risposte:
Non sono un esperto di implementazioni linguistiche (quindi prendilo con le pinze), ma penso che uno dei costi maggiori sia srotolare lo stack e memorizzarlo per la traccia dello stack. Sospetto che ciò accada solo quando viene lanciata l'eccezione (ma non lo so), e in tal caso, questo sarebbe un costo nascosto di dimensioni adeguate ogni volta che viene lanciata un'eccezione ... quindi non è come se stessi saltando da un posto nel codice a un altro, c'è molto da fare.
Non penso che sia un problema fintanto che si utilizzano eccezioni per comportamenti ECCEZIONALI (quindi non il tipico percorso previsto attraverso il programma).
Tre punti da sottolineare qui:
In primo luogo, c'è poca o NESSUNA penalizzazione delle prestazioni nell'avere effettivamente blocchi try-catch nel codice. Questo non dovrebbe essere preso in considerazione quando si cerca di evitare di averli nella propria applicazione. Il risultato positivo entra in gioco solo quando viene generata un'eccezione.
Quando viene lanciata un'eccezione in aggiunta alle operazioni di srotolamento dello stack, ecc. Che si verificano, che altri hanno menzionato, dovresti essere consapevole che un intero gruppo di cose relative a runtime / riflessione accade per popolare i membri della classe di eccezione come la traccia dello stack oggetto e i vari membri del tipo ecc.
Credo che questo sia uno dei motivi per cui il consiglio generale se hai intenzione di rilanciare l'eccezione è semplicemente throw;
piuttosto che lanciare di nuovo l'eccezione o costruirne una nuova poiché in quei casi tutte le informazioni sullo stack vengono raccolte mentre nel semplice buttalo è tutto conservato.
throw new Exception("Wrapping layer’s error", ex);
Stai chiedendo informazioni sull'overhead dell'utilizzo di try / catch / latest quando le eccezioni non vengono generate o sull'overhead dell'utilizzo delle eccezioni per controllare il flusso del processo? Quest'ultimo è in qualche modo simile all'uso di un candelotto di dinamite per accendere la candela di compleanno di un bambino, e l'overhead associato rientra nelle seguenti aree:
È possibile aspettarsi errori di pagina aggiuntivi a causa dell'eccezione generata che accede a codice e dati non residenti normalmente non presenti nel working set dell'applicazione.
entrambi gli elementi sopra in genere accedono al codice e ai dati "freddi", quindi sono probabili errori di pagina rigida se si dispone di una pressione della memoria:
Per quanto riguarda l'impatto effettivo del costo, questo può variare molto a seconda di cos'altro sta succedendo nel tuo codice in quel momento. Jon Skeet ha un buon riassunto qui , con alcuni link utili. Tendo ad essere d'accordo con la sua affermazione secondo cui se arrivi al punto in cui le eccezioni danneggiano in modo significativo la tua prestazione, hai problemi in termini di utilizzo delle eccezioni oltre la semplice prestazione.
Nella mia esperienza il sovraccarico più grande è effettivamente lanciare un'eccezione e gestirla. Una volta ho lavorato a un progetto in cui è stato utilizzato un codice simile al seguente per verificare se qualcuno aveva il diritto di modificare un oggetto. Questo metodo HasRight () è stato utilizzato ovunque nel livello di presentazione ed è stato spesso chiamato per centinaia di oggetti.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Quando il database di test è diventato più pieno di dati di test, questo ha portato a un rallentamento molto visibile durante l'apertura di nuovi moduli, ecc.
Quindi l'ho riformattato nel modo seguente, che - secondo le misurazioni successive di quick 'n dirty - è circa 2 ordini di grandezza più veloce:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Quindi, in breve, l'utilizzo delle eccezioni nel flusso di processo normale è circa due ordini di grandezza più lento rispetto all'utilizzo di un flusso di processo simile senza eccezioni.
Contrariamente alle teorie comunemente accettate, try
/ catch
può avere implicazioni significative sulle prestazioni, e questo è se viene generata un'eccezione o meno!
Il primo è stato trattato in un paio di post di blog da Microsoft MVP nel corso degli anni e confido che potresti trovarli facilmente, ma StackOverflow si preoccupa così tanto dei contenuti, quindi fornirò collegamenti ad alcuni di essi come prova di riempimento :
try
/ catch
/finally
( e la seconda parte ), di Peter Ritchie esplora le ottimizzazioni chetry
/catch
/finally
disabilita (e approfondirò questo argomento con citazioni dallo standard)Parse
vs. TryParse
vs.ConvertTo
di Ian Huff afferma sfacciatamente che "la gestione delle eccezioni è molto lenta" e dimostra questo punto mettendosi aconfrontoInt.Parse
eInt.TryParse
uno contro l'altro ... Per chiunque insista sul fatto cheTryParse
usitry
/catch
dietro le quinte, questo dovrebbe far luce!C'è anche questa risposta che mostra la differenza tra il codice disassemblato con e senza l'utilizzo di try
/ catch
.
Sembra così ovvio che ci sia un sovraccarico che è palesemente osservabile nella generazione del codice e che sembra persino essere riconosciuto da persone che Microsoft apprezza! Eppure sto ripetendo Internet ...
Sì, ci sono dozzine di istruzioni MSIL extra per una banale riga di codice, e questo non copre nemmeno le ottimizzazioni disabilitate, quindi tecnicamente è una microottimizzazione.
Ho postato una risposta anni fa che è stata cancellata in quanto si concentrava sulla produttività dei programmatori (l'ottimizzazione macro).
Questo è un peccato perché è probabile che nessun risparmio di pochi nanosecondi qua e là di tempo della CPU compensi molte ore accumulate di ottimizzazione manuale da parte degli esseri umani. Per cosa paga di più il tuo capo: un'ora del tuo tempo o un'ora con il computer acceso? A che punto stacciamo la spina e ammettiamo che è ora di comprare un computer più veloce ?
Chiaramente, dovremmo ottimizzare le nostre priorità , non solo il nostro codice! Nella mia ultima risposta ho attinto alle differenze tra due frammenti di codice.
Utilizzando try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Non utilizzando try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Considera dal punto di vista di uno sviluppatore di manutenzione, che è più probabile che ti faccia perdere tempo, se non nella profilazione / ottimizzazione (trattata sopra) che probabilmente non sarebbe nemmeno necessaria se non fosse per il try
/ catch
problema, quindi nello scorrimento codice sorgente ... Uno di questi ha quattro righe extra di immondizia boilerplate!
Man mano che sempre più campi vengono introdotti in una classe, tutta questa immondizia standard si accumula (sia nel codice sorgente che nel codice disassemblato) ben oltre i livelli ragionevoli. Quattro righe in più per campo, e sono sempre le stesse righe ... Non ci è stato insegnato a evitare di ripeterci? Suppongo che potremmo nascondere il try
/ catch
dietro qualche astrazione fatta in casa, ma ... allora potremmo anche evitare le eccezioni (cioè l'uso Int.TryParse
).
Questo non è nemmeno un esempio complesso; Ho visto tentativi di istanziare nuove classi in try
/ catch
. Considera che tutto il codice all'interno del costruttore potrebbe quindi essere squalificato da alcune ottimizzazioni che altrimenti verrebbero applicate automaticamente dal compilatore. Quale modo migliore per dare origine alla teoria secondo cui il compilatore è lento , al contrario del compilatore che fa esattamente quello che gli viene detto di fare ?
Supponendo che venga lanciata un'eccezione da tale costruttore e che di conseguenza venga attivato qualche bug, il povero sviluppatore di manutenzione deve quindi rintracciarlo. Potrebbe non essere un compito così facile, poiché a differenza del codice spaghetti di goto nightmare, try
/ catch
può causare confusione in tre dimensioni , poiché potrebbe spostarsi in alto nello stack non solo in altre parti dello stesso metodo, ma anche in altre classi e metodi , che sarà osservato dallo sviluppatore della manutenzione, nel modo più duro ! Eppure ci viene detto che "goto è pericoloso", eh!
Alla fine menziono, try
/ catch
ha il suo vantaggio che è progettato per disabilitare le ottimizzazioni ! È, se vuoi, un aiuto per il debugging ! Questo è ciò per cui è stato progettato ed è ciò che dovrebbe essere usato come ...
Immagino che anche questo sia un punto positivo. Può essere utilizzato per disabilitare le ottimizzazioni che altrimenti potrebbero paralizzare algoritmi di passaggio di messaggi sicuri e sani per le applicazioni multithread e per rilevare possibili condizioni di competizione;) Questo è l'unico scenario a cui riesco a pensare di usare try / catch. Anche questo ha delle alternative.
Cosa fare ottimizzazioni try
, catch
e finally
disabilitare?
AKA
Come stai try
, catch
e finally
utili come il debug aiuti?
sono barriere in scrittura. Questo viene dallo standard:
12.3.3.13 Istruzioni try-catch
Per una dichiarazione stmt del modulo:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- Lo stato di assegnazione definita di v all'inizio del blocco try è uguale allo stato di assegnazione definita di v all'inizio di stmt .
- Lo stato di assegnazione definita di v all'inizio di catch-block-i (per ogni i ) è uguale allo stato di assegnazione definita di v all'inizio di stmt .
- Lo stato di assegnazione definito di v al punto finale di stmt è definitivamente assegnato se (e solo se) v è assegnato definitivamente al punto finale di try-block e ogni catch-block-i (per ogni i da 1 a n ).
In altre parole, all'inizio di ogni try
affermazione:
try
istruzione devono essere complete, il che richiede un thread lock per iniziare, rendendolo utile per il debug delle race condition!try
dell'istruzioneUna storia simile vale per ogni catch
affermazione; supponiamo che all'interno della tua try
istruzione (o un costruttore o una funzione che invoca, ecc.) tu assegni a quella variabile altrimenti inutile (diciamo, garbage=42;
), il compilatore non può eliminare quell'istruzione, non importa quanto sia irrilevante per il comportamento osservabile del programma . L'assegnazione deve essere completata prima di catch
inserire il blocco.
Per quello che vale, finally
racconta una storia altrettanto degradante :
12.3.3.14 Istruzioni di prova
Per una dichiarazione di prova stmt del modulo:
try try-block finally finally-block
• Lo stato di assegnazione definito di v all'inizio di try-block è uguale allo stato di assegnazione definita di v all'inizio di stmt .
• Lo stato di assegnazione definita di v all'inizio del blocco alla fine è uguale allo stato di assegnazione definita di v all'inizio di stmt .
• Lo stato di assegnazione definito di v al punto finale di stmt è assegnato definitivamente se (e solo se): o v è assegnato definitivamente al punto finale del blocco try o vè definitivamente assegnato al punto finale del blocco finale Se viene effettuato un trasferimento del flusso di controllo (come un'istruzione goto ) che inizia all'interno del blocco try e termina all'esterno del blocco try , allora anche v è considerato definitivamente assegnato su quello controllare il trasferimento del flusso se v è definitivamente assegnato al punto finale del blocco finale . (Questo non è un solo se, se v è assegnato definitivamente per un altro motivo in questo trasferimento del flusso di controllo, allora è ancora considerato assegnato definitivamente.)
12.3.3.15 Istruzioni Try-catch-latest
Analisi assegnazione definitiva per una prova - fermo - finalmente dichiarazione del modulo:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
è fatto come se l'istruzione fosse un'istruzione try - latest che racchiude un'istruzione try - catch :
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Per non parlare del fatto che si trovi all'interno di un metodo chiamato di frequente, può influenzare il comportamento generale dell'applicazione.
Ad esempio, considero l'uso di Int32.Parse come una cattiva pratica nella maggior parte dei casi poiché genera eccezioni per qualcosa che può essere catturato facilmente altrimenti.
Quindi, per concludere tutto ciò che è scritto qui:
1) Usa i blocchi try..catch per rilevare errori imprevisti - quasi nessuna penalizzazione delle prestazioni.
2) Non usare eccezioni per errori esclusi se puoi evitarlo.
Ho scritto un articolo su questo un po 'di tempo fa perché c'erano molte persone che lo chiedevano in quel momento. Puoi trovarlo e il codice di prova su http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx .
Il risultato è che c'è una piccola quantità di overhead per un blocco try / catch ma così piccola che dovrebbe essere ignorata. Tuttavia, se stai eseguendo blocchi try / catch in cicli che vengono eseguiti milioni di volte, potresti prendere in considerazione di spostare il blocco all'esterno del ciclo, se possibile.
Il problema di prestazioni chiave con i blocchi try / catch è quando si rileva effettivamente un'eccezione. Ciò può aggiungere un notevole ritardo alla tua applicazione. Ovviamente, quando le cose vanno male, la maggior parte degli sviluppatori (e molti utenti) riconoscono la pausa come un'eccezione che sta per accadere! La chiave qui non è utilizzare la gestione delle eccezioni per le normali operazioni. Come suggerisce il nome, sono eccezionali e dovresti fare tutto il possibile per evitare che vengano lanciati. Non dovresti usarli come parte del flusso previsto di un programma che funziona correttamente.
L' anno scorso ho scritto un post su questo argomento. Controlla. La conclusione è che non c'è quasi alcun costo per un blocco try se non si verifica alcuna eccezione - e sul mio laptop, un'eccezione era di circa 36μs. Potrebbe essere inferiore a quanto ti aspettavi, ma tieni presente che quei risultati erano su uno stack poco profondo. Inoltre, le prime eccezioni sono molto lente.
try
/ catch
troppo? Eh eh), ma sembra che tu stia discutendo con le specifiche della lingua e alcuni MS MVP che hanno anche scritto blog sull'argomento, fornendo misurazioni al contrario dei tuoi consigli ... Sono aperto al suggerimento che la ricerca che ho fatto sia sbagliata, ma dovrò leggere il tuo post sul blog per vedere cosa dice.
try-catch
blocco vs tryparse()
metodi, ma il concetto è lo stesso.
È molto più facile scrivere, eseguire il debug e mantenere il codice privo di messaggi di errore del compilatore, messaggi di avviso di analisi del codice ed eccezioni accettate di routine (in particolare eccezioni che vengono lanciate in un punto e accettate in un altro). Poiché è più semplice, il codice sarà in media scritto meglio e con meno bug.
Per me, quel programmatore e il sovraccarico di qualità sono l'argomento principale contro l'utilizzo di try-catch per il flusso di processo.
Il sovraccarico del computer delle eccezioni è insignificante in confronto e solitamente minimo in termini di capacità dell'applicazione di soddisfare i requisiti di prestazioni del mondo reale.
Mi piace molto il post sul blog di Hafthor e, per aggiungere i miei due centesimi a questa discussione, vorrei dire che è sempre stato facile per me fare in modo che DATA LAYER generi un solo tipo di eccezione (DataAccessException). In questo modo il mio BUSINESS LAYER sa quale eccezione aspettarsi e la rileva. Quindi, a seconda di ulteriori regole di business (ad esempio se il mio oggetto di business partecipa al flusso di lavoro, ecc.), Posso lanciare una nuova eccezione (BusinessObjectException) o procedere senza ripetere / lanciare.
Direi di non esitare a usare try..catch ogni volta che è necessario e usalo con saggezza!
Ad esempio, questo metodo partecipa a un flusso di lavoro ...
Commenti?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
Possiamo leggere in Programming Languages Pragmatics di Michael L. Scott che i compilatori di oggi non aggiungono alcun overhead nel caso comune, questo significa che non si verificano eccezioni. Quindi ogni lavoro viene eseguito in fase di compilazione. Ma quando viene lanciata un'eccezione in fase di esecuzione, il compilatore deve eseguire una ricerca binaria per trovare l'eccezione corretta e questo accadrà per ogni nuovo lancio che hai fatto.
Ma le eccezioni sono eccezioni e questo costo è perfettamente accettabile. Se provi a eseguire la gestione delle eccezioni senza eccezioni e utilizzi invece codici di errore di ritorno, probabilmente avrai bisogno di un'istruzione if per ogni subroutine e ciò incorrerà in un sovraccarico in tempo reale. Sai che un'istruzione if viene convertita in poche istruzioni di assemblaggio, che verranno eseguite ogni volta che entri nelle tue sub-routine.
Mi dispiace per il mio inglese, spero che ti aiuti. Queste informazioni si basano sul libro citato, per ulteriori informazioni fare riferimento al Capitolo 8.5 Gestione delle eccezioni.
Analizziamo uno dei maggiori costi possibili di un blocco try / catch quando viene utilizzato dove non dovrebbe essere necessario:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Ed ecco quello senza try / catch:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Senza contare lo spazio vuoto insignificante, si potrebbe notare che questi due pezzi di codice equivalenti hanno quasi esattamente la stessa lunghezza in byte. Quest'ultimo contiene 4 byte in meno di rientro. È una brutta cosa?
Per aggiungere la beffa al danno, uno studente decide di eseguire il loop mentre l'input può essere analizzato come un int. La soluzione senza try / catch potrebbe essere qualcosa del tipo:
while (int.TryParse(...))
{
...
}
Ma come appare quando si usa try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
I blocchi di prova / cattura sono modi magici per sprecare il rientro e ancora non sappiamo nemmeno il motivo per cui ha fallito! Immagina come si sente la persona che esegue il debug, quando il codice continua ad essere eseguito oltre un grave difetto logico, invece di fermarsi con un simpatico errore di eccezione evidente. I blocchi try / catch sono la convalida / sanificazione dei dati di un uomo pigro.
Uno dei costi minori è che i blocchi try / catch disabilitano effettivamente alcune ottimizzazioni: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx . Immagino che anche questo sia un punto positivo. Può essere utilizzato per disabilitare le ottimizzazioni che altrimenti potrebbero paralizzare algoritmi di passaggio di messaggi sicuri e sani per le applicazioni multithread e per rilevare possibili condizioni di competizione;) Questo è l'unico scenario a cui riesco a pensare di usare try / catch. Anche questo ha delle alternative.
Int.Parse
a favore di Int.TryParse
.