Domanda

Quindi, so che try/catch aggiunge un sovraccarico e quindi non è un buon modo per controllare il flusso del processo, ma da dove viene questo sovraccarico e qual è il suo impatto effettivo?

È stato utile?

Soluzione

Non sono un esperto di implementazioni linguistiche (quindi prendilo con le pinze), ma penso che uno dei costi maggiori sia lo svolgimento dello stack e l'archiviazione per l'analisi dello stack.Sospetto che ciò accada solo quando viene lanciata un'eccezione (ma non lo so), e se così fosse, questo sarebbe un costo nascosto di dimensioni decenti ogni volta che viene lanciata un'eccezione...quindi non è che stai semplicemente saltando da un punto all'altro del codice, c'è molto da fare.

Non penso che sia un problema fintanto che usi le eccezioni per comportamenti ECCEZIONALI (quindi non il tipico percorso previsto attraverso il programma).

Altri suggerimenti

Tre punti da sottolineare qui:

  • In primo luogo, c'è poca o nessuna penalità in termini di 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 successo prestazionale entra in gioco solo quando viene lanciata un'eccezione.

  • Quando viene lanciata un'eccezione in aggiunta alle operazioni di rimozione dello stack ecc. che hanno luogo e che altri hanno menzionato, dovresti essere consapevole che si verificano tutta una serie di cose relative al runtime/riflessione per popolare i membri della classe di eccezioni come l'analisi dello stack oggetto e i vari membri del tipo ecc.

  • Credo che questo sia uno dei motivi per cui il consiglio generale se si intende rilanciare l'eccezione è giusto throw; piuttosto che lanciare nuovamente l'eccezione o costruirne una nuova poiché in questi casi tutte le informazioni sullo stack vengono raccolte mentre nel semplice lancio vengono tutte preservate.

Stai chiedendo informazioni sul sovraccarico derivante dall'utilizzo di try/catch/finally quando le eccezioni non vengono generate o sul sovraccarico derivante dall'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 le spese generali associate rientrano nelle seguenti aree:

  • È possibile aspettarsi ulteriori errori nella cache a causa dell'eccezione generata che accede ai dati residenti normalmente non nella cache.
  • È possibile che vengano visualizzati ulteriori errori di pagina dovuti all'eccezione generata che accede a codice non residente e dati normalmente non presenti nel working set dell'applicazione.

    • ad esempio, lanciare l'eccezione richiederà al CLR di trovare la posizione dei blocchi finali e di cattura in base all'IP corrente e all'IP restituito di ogni frame finché non viene gestita l'eccezione più il blocco del filtro.
    • costi aggiuntivi di costruzione e risoluzione dei nomi per creare i frame per scopi diagnostici, inclusa la lettura di metadati, ecc.
    • entrambi gli elementi di cui sopra in genere accedono a codice e dati "freddi", quindi sono probabili errori di pagina se si ha un sovraccarico di memoria:

      • il CLR tenta di mettere il codice e i dati utilizzati di rado lontano dai dati utilizzati frequentemente per migliorare la località, quindi questo funziona contro di te perché stai forzando il freddo a essere caldo.
      • il costo degli errori di pagina, se presenti, farà impallidire tutto il resto.
  • Le situazioni tipiche di cattura sono spesso profonde, quindi gli effetti di cui sopra tenderebbero ad essere amplificati (aumentando la probabilità di errori di pagina).

Per quanto riguarda l'impatto effettivo del costo, questo può variare molto a seconda di cos'altro sta accadendo nel codice in quel momento.Jon Skeet ha una 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 performance, hai problemi in termini di utilizzo delle eccezioni oltre la semplice performance.

Nella mia esperienza, il sovraccarico maggiore consiste nel lanciare effettivamente un'eccezione e gestirla.Una volta ho lavorato su un progetto in cui veniva utilizzato un codice simile al seguente per verificare se qualcuno aveva il diritto di modificare alcuni oggetti.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 si è riempito di dati di test, ciò ha comportato un rallentamento molto visibile durante l'apertura di nuovi moduli, ecc.

Quindi l'ho rifattorizzato come segue, che, secondo le misurazioni rapide e sporche successive, è 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 normale flusso di processo è circa due ordini di grandezza più lento rispetto all'utilizzo di un flusso di processo simile senza eccezioni.

Per non parlare del fatto che se si trova all'interno di un metodo chiamato di frequente potrebbe influire sul 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 altrimenti potrebbe essere rilevato facilmente.

Quindi per concludere tutto quanto scritto qui:
1) Utilizza i blocchi try..catch per individuare errori imprevisti, quasi senza penalità in termini di prestazioni.
2) Non utilizzare eccezioni per errori esclusi se puoi evitarlo.

Ho scritto un articolo su questo argomento qualche tempo fa perché c'erano molte persone che me lo chiedevano in quel momento.Puoi trovarlo insieme al codice di prova su http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx.

Il risultato è che c'è un piccolo sovraccarico per un blocco try/catch ma così piccolo che dovrebbe essere ignorato.Tuttavia, se stai eseguendo blocchi try/catch in cicli eseguiti milioni di volte, potresti prendere in considerazione l'idea di spostare il blocco all'esterno del ciclo, se possibile.

Il problema chiave delle prestazioni con i blocchi try/catch è quando si rileva effettivamente un'eccezione.Ciò può aggiungere un notevole ritardo alla tua domanda.Naturalmente, quando le cose vanno male, la maggior parte degli sviluppatori (e molti utenti) riconoscono la pausa come un'eccezione che sta per verificarsi!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.

ho fatto un voce del blog su questo argomento l'anno scorso.Controlla.La conclusione è che non vi è 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 si trovavano su uno stack poco profondo.Inoltre, le prime eccezioni sono molto lente.

È molto più semplice scrivere, eseguire il debug e mantenere un codice privo di messaggi di errore del compilatore, messaggi di avviso di analisi del codice ed eccezioni accettate di routine (in particolare eccezioni generate in un posto e accettate in un altro).Poiché è più semplice, il codice sarà in media scritto meglio e con meno errori.

Per me, il sovraccarico del programmatore e della qualità è l'argomento principale contro l'utilizzo di try-catch per il flusso del processo.

Il sovraccarico del computer dovuto alle eccezioni è insignificante in confronto e solitamente minimo in termini di capacità dell'applicazione di soddisfare i requisiti di prestazioni del mondo reale.

Contrariamente alle teorie comunemente accettate, try/catch può avere implicazioni significative sulle prestazioni, e questo dipende dal fatto che venga generata o meno un'eccezione!

  1. Disabilita alcune ottimizzazioni automatiche (in base alla progettazione), e in alcuni casi inietta debug codice, come puoi aspettarti da a aiuto per il debug.Ci saranno sempre persone che non saranno d'accordo con me su questo punto, ma il linguaggio lo richiede e lo smontaggio lo dimostra quindi quelle persone sono per definizione del dizionario delirante.
  2. Può avere un impatto negativo sulla manutenzione. Questo è in realtà il problema più significativo qui, ma poiché la mia ultima risposta (che si concentrava quasi interamente su di esso) è stata cancellata, cercherò di concentrarmi sul problema meno significativo (la micro-ottimizzazione) rispetto a quello più significativo ( la macroottimizzazione).

Il primo è stato trattato in un paio di post sul blog di Microsoft MVP nel corso degli anni e confido che potresti trovarli facilmente, ma StackOverflow se ne frega tanto Di contenuto quindi fornirò i collegamenti ad alcuni di essi come riempitivo prova:

C'è anche questa risposta che mostra la differenza tra codice disassemblato con e senza utilizzo try/catch.

Sembra così ovvio che lì È un sovraccarico che è palesemente osservabile nella generazione del codice e tale sovraccarico sembra essere riconosciuto anche dalle persone che Microsoft apprezza!Eppure lo sono, 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 micro-ottimizzazione.


Anni fa ho pubblicato una risposta che è stata cancellata perché incentrata sulla produttività dei programmatori (la macroottimizzazione).

Questo è un peccato perché il mancato risparmio di qualche nanosecondo qua e là del tempo della CPU probabilmente compenserà molte ore accumulate di ottimizzazione manuale da parte degli esseri umani.Per quali cose il tuo capo paga di più:un'ora del tuo tempo o un'ora con il computer acceso?A che punto stacchiamo la spina e ammettiamo che è ora di farlo? acquistare un computer più veloce?

Chiaramente, dovremmo esserlo ottimizzando 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 utilizzare 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 (di cui sopra) che probabilmente non sarebbe nemmeno necessaria se non fosse per il try/catch problema, quindi scorrendo il codice sorgente...Uno di questi ha quattro righe extra di spazzatura standard!

Man mano che sempre più campi vengono introdotti in una classe, tutta questa spazzatura standard si accumula (sia nel codice sorgente che in quello 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 (ad es.utilizzo Int.TryParse).

Questo non è nemmeno un esempio complesso;Ho visto tentativi di istanziare nuove classi in try/catch.Considerare 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 che il compilatore è lento, al contrario di il compilatore sta facendo esattamente quello che gli viene detto di fare?

Supponendo che venga lanciata un'eccezione da detto costruttore e di conseguenza venga attivato qualche bug, lo sviluppatore di manutenzione scadente deve quindi rintracciarlo.Potrebbe non essere un compito così facile, a differenza dello spaghetti code del file vai a incubo, try/catch può causare disordini tre dimensioni, poiché potrebbe risalire lo stack non solo in altre parti dello stesso metodo, ma anche in altre classi e metodi, che verranno tutti osservati 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, a aiuto per il debug!Questo è lo scopo per cui è stato progettato ed è ciò per cui dovrebbe essere utilizzato...

Immagino che anche questo sia un punto positivo.Può essere utilizzato per disabilitare ottimizzazioni che altrimenti potrebbero paralizzare algoritmi di passaggio di messaggi sani e sicuri per applicazioni multithread e per rilevare possibili condizioni di competizione;) Questo è l'unico scenario che mi viene in mente per utilizzare try/catch.Anche quello ha delle alternative.


Cosa fanno le ottimizzazioni try, catch E finally disattivare?

AKA

Come sono try, catch E finally utile come ausilio per il debug?

sono barriere alla scrittura.Questo deriva dallo standard:

12.3.3.13 Dichiarazioni try-catch

Per una dichiarazione stmt del modulo:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
  • Lo stato di assegnazione definito di v all'inizio di prova-blocco è uguale allo stato di assegnazione definito di v all'inizio di stmt.
  • Lo stato di assegnazione definito di v all'inizio di catch-block-i (per ogni io) è uguale allo stato di assegnazione definito di v all'inizio di stmt.
  • Lo stato di assegnazione definito di v al punto finale di stmt è assegnato definitivamente se (e solo se) v è definitivamente assegnato al punto finale di prova-blocco e ogni catch-block-i (per ogni io da 1 a N).

In altre parole, all'inizio di ciascuno try dichiarazione:

  • tutte le assegnazioni effettuate agli oggetti visibili prima di entrare nel file try deve essere completa, il che richiede un thread lock per l'avvio, rendendola utile per il debug delle condizioni di gara!
  • al compilatore non è consentito:
    • eliminare le assegnazioni di variabili inutilizzate a cui sono state assegnate definitivamente prima del try dichiarazione
    • riorganizzare o unire qualcuno di esso compiti interiori (cioè.vedi il mio primo link, se non l'hai già fatto).
    • sollevare le assegnazioni oltre questa barriera, ritardare l'assegnazione a una variabile che sa che non verrà utilizzata se non in seguito (se non del tutto) o spostare preventivamente in avanti le assegnazioni successive per rendere possibili altre ottimizzazioni...

Per ciascuno di essi vale una storia simile catch dichiarazione;supponi nel tuo try (o un costruttore o una funzione che invoca, ecc.) che 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'incarico deve avere completato prima di catch viene inserito il blocco.

Per quello che vale, finally racconta una cosa simile degradante storia:

12.3.3.14 Dichiarazioni try-finally

Per un Tentativo dichiarazione stmt del modulo:

try try-block
finally finally-block

• Lo stato di assegnazione definitivo di v all'inizio di prova-blocco è uguale allo stato di assegnazione definito di v all'inizio di stmt.
• Lo stato di assegnazione definitivo di v all'inizio di infine-blocca è uguale allo stato di assegnazione definito di v all'inizio di stmt.
• Lo stato di assegnazione definitivo di v al punto finale di stmt è definitivamente assegnato se (e solo se):o v è definitivamente assegnato al punto finale di prova-bloccoo v è definitivamente assegnato al punto finale di infine-bloccaSe un trasferimento del flusso di controllo (come a vai a dichiarazione) che inizia all'interno prova-blocco, e termina all'esterno di prova-blocco, Poi v è considerato assegnato definitivamente anche in occasione del trasferimento del flusso di controllo se v è definitivamente assegnato al punto finale di infine-blocca.(Questo non è un solo se-se v viene assegnato definitivamente per un altro motivo in questo trasferimento del flusso di controllo, viene comunque considerato assegnato definitivamente.)

12.3.3.15 Dichiarazioni try-catch-finally

Analisi di assegnazione definita per a Tentativo-presa-Finalmente dichiarazione del modulo:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
finally finally-block

è fatto come se l'affermazione fosse a Tentativo-Finalmente dichiarazione allegata a Tentativo-presa dichiarazione:

try { 
    try   
    try-block
    catch ( ... ) catch-block-1
    ...   
    catch ( ... ) catch-block-n
} 
finally finally-block

Mi piace molto quello di Hafthor post sul blog, e per aggiungere i miei due centesimi a questa discussione, vorrei dire che è sempre stato facile per me fare in modo che DATA LAYER lanciasse solo un tipo di eccezione (DataAccessException).In questo modo il mio BUSINESS LAYER sa quale eccezione aspettarsi e la rileva.Quindi, a seconda di ulteriori regole aziendali (ad es.se il mio oggetto aziendale partecipa al flusso di lavoro, ecc.), posso lanciare una nuova eccezione (BusinessObjectException) o procedere senza rilanciare.

Direi di non esitare a usare try..catch ogni volta che è necessario e usalo saggiamente!

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 attuali non aggiungono alcun sovraccarico nel caso comune, cioè quando non si verificano eccezioni.Quindi ogni lavoro viene realizzato in fase di compilazione.Ma quando viene generata un'eccezione in fase di esecuzione, il compilatore deve eseguire una ricerca binaria per trovare l'eccezione corretta e ciò accadrà per ogni nuovo lancio effettuato.

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ò comporterà un sovraccarico in tempo reale.Sai che un'istruzione if viene convertita in alcune istruzioni di assembly, 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 utilizzato:

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 gli insignificanti spazi bianchi, si potrebbe notare che questi due pezzi di codice equivalenti hanno quasi esattamente la stessa lunghezza in byte.Quest'ultimo contiene 4 byte di rientro in meno.È una brutta cosa?

Per aggiungere la beffa al danno, uno studente decide di eseguire il loop mentre l'input può essere analizzato come int.La soluzione senza try/catch potrebbe essere qualcosa del tipo:

while (int.TryParse(...))
{
    ...
}

Ma come appare quando si utilizza try/catch?

try {
    for (;;)
    {
        x = int.Parse(...);
        ...
    }
}
catch
{
    ...
}

I blocchi Try/catch sono modi magici per sprecare il rientro e non sappiamo nemmeno il motivo per cui hanno fallito!Immagina come si sente la persona che esegue il debug, quando il codice continua a essere eseguito oltre un grave difetto logico, invece di fermarsi con un evidente errore di eccezione.I blocchi try/catch sono la convalida/risanamento 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 ottimizzazioni che altrimenti potrebbero paralizzare algoritmi di passaggio di messaggi sani e sicuri per applicazioni multithread e per rilevare possibili condizioni di competizione;) Questo è l'unico scenario che mi viene in mente per utilizzare try/catch.Anche quello ha delle alternative.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top