2009-08-15 12 views
16

E 'possibile modificare questo codice, con un valore di ritorno e un'eccezione:Quanto più costosa è un'eccezione che un valore di ritorno?

public Foo Bar(Bar b) 
{ 
    if(b.Success) 
    { 
     return b; 
    } 
    else 
    { 
     throw n.Exception; 
    } 
} 

a questo, che genera eccezioni separate per il successo e il fallimento

public Foo Bar(Bar b) 
{ 
    throw b.Success ? new BarException(b) : new FooException(); 
} 

try 
{ 
    Bar(b) 
} 
catch(BarException bex) 
{ 
    return ex.Bar; 
} 
catch(FooException fex) 
{ 
    Console.WriteLine(fex.Message); 
} 
+0

Questo sembra essere un duplicato funzionale di http://stackoverflow.com/questions/99683/which-and-why-do-you-prefer- codici di eccezione o di ritorno – dmckee

+0

Indipendentemente dalla risposta, è importante ricordare che il costo è relativo all'intero programma. Potresti scoprire che una porzione di codice di 3 righe è più costosa di 1000 volte quando lanci un'eccezione il 100% delle volte. Ma la realtà è che l'eccezione può essere lanciata solo l'1% del tempo in una soluzione che fa cose come chiamate di database o file di lettura. –

risposta

21

un'eccezione è sicuramente più costoso di restituire un valore. Ma in termini di costo grezzo è difficile dire quanto sia più costosa un'eccezione.

Quando si decide un valore di ritorno rispetto a un'eccezione, si dovrebbe sempre considerare la seguente regola.

Utilizzare solo eccezioni per i casi eccezionali

Non dovrebbero mai essere utilizzati per il flusso di controllo generale.

+6

Ci sono momenti in cui le eccezioni hanno senso per il controllo generale del flusso - flusso di controllo per il quale la lingua ha meno alternative. Ad esempio, considera un parser di discesa ricorsivo che sta studiando un percorso di analisi speculativamente - quando si arrende, vorrai tornare da un albero delle chiamate molto profondo, e il modo più semplice per farlo senza una progettazione faticosa di ogni routine è lanciare un'eccezione. –

+1

Non sono d'accordo con "Usa solo eccezioni per circostanze eccezionali" Questa regola è molto vaga e soggettiva per essere efficace. Lanciare una "InvalidParameterException" non è certo una circostanza eccezionale. – Alan

+11

@Alan: passare un argomento non valido a un metodo * dovrebbe * essere una circostanza eccezionale. –

1

In alcune recenti analisi delle prestazioni di lavoro reali, abbiamo riscontrato che tonnellate di eccezioni su macchine di fascia bassa hanno avuto un effetto critico sulle prestazioni dell'applicazione, tanto che ci stiamo dedicando alcune settimane per passare e regolare il codice per non buttare così tanti

Quando dico un effetto critico, era nel campo da baseball di spiking dual core CPU fino al 95% sul nucleo singola CPU l'applicazione stava usando.

+0

A cosa serve il voto negativo? –

2

Ho problemi al momento di trovare tutti i documenti per sostenerlo, ma tenere a mente che quando si lancia un'eccezione, C# deve generare una traccia dello stack dal punto in cui è chiamato. Le tracce dello stack (e il riflesso in generale) sono tutt'altro che gratuiti.

+1

La raccolta dello stack tecnicamente non è così costosa. Certamente costa qualcosa se i dati provenienti dall'alto dello stack (stack in crescita) devono essere tirati nella cache per afferrare ogni indirizzo di ritorno, ma questo è l'unico costo che deve essere pagato finché qualcuno non chiede effettivamente i dati di traccia dello stack. Puoi fare pigramente il riflesso e non pagare mai se nessuno ti chiede. –

1

si dovrebbe preferire eccezioni oltre i codici di errore, e per le condizioni di errore, ma non utilizzare le eccezioni per il normale flusso del programma.

Le eccezioni sono estremamente resistente rispetto al normale flusso di lavoro, ho visto un enorme ordine di grandezza in diminuzione le prestazioni delle applicazioni utilizzando un try-catch-block.

2

Utilizzando i rendimenti di errore sarà molto più costoso di eccezioni - non appena un pezzo di codice si dimentica di controllare il ritorno di errore, o non riesce a propagarla.

Eppure, essere sicuri di non utilizzare le eccezioni per il controllo di flusso - usarli solo per indicare che qualcosa è andato storto.

4

È possibile trovare un sacco di informazioni utili su questo le risposte a questa domanda, tra cui una risposta con il 45 up-voti How slow are .net exceptions?

+0

Quale sembra essere errato ... – user139593

+0

Hmmm, quale parte della mia risposta non è corretta, angryboy? L'URL, le risposte a questa domanda, il fatto che Jon avesse 45 voti positivi (ora 49), o la mia affermazione che ci sono "molte informazioni utili" lì? – DOK

+0

Non ho detto che nessuna parte della tua risposta era DOK errata. Se l'avessi voluto, allora avrei detto "* questo * non è corretto". Piuttosto, stavo rispondendo al tuo riferimento: "... includendo una risposta con 45 voti positivi", con: "Che sembra non essere corretto.", Come in, il contenuto della risposta a cui stai facendo riferimento sembra essere errato . – RBarryYoung

7

Eccezione avere due costi: warm-up alla pagina nell'infrastruttura un'eccezione - se non nella memoria quindi nella cache della CPU - e per il costo per raccogliere lo stack delle eccezioni, cercare il gestore delle eccezioni, eventualmente richiamare i filtri delle eccezioni, sganciare lo stack, chiamare i blocchi di finalizzazione - tutte le operazioni che il runtime, in base alla progettazione, non ottimizza per.

Pertanto, la misurazione del costo di lancio delle eccezioni può essere fuorviante. Se scrivi un ciclo che gira in modo iterativo e ottiene un'eccezione, senza molto lavoro tra il sito di lancio e il sito di cattura, il costo non sarà così grande. Tuttavia, è perché sta ammortizzando il costo di riscaldamento delle eccezioni e questo costo è più difficile da misurare.

Certamente, le eccezioni non costano nulla come sembrano se l'esperienza principale è costituita dalle eccezioni generate dai programmi sotto il debugger. Ma costano, ed è consigliabile progettare le librerie in particolare in modo tale che le eccezioni possano essere evitate dove necessario per l'ottimizzazione.

+0

È la presa che incorre nel costo per raccogliere lo stack, cercare i gestori, ecc. Non il lancio. –

+0

La raccolta della pila può essere relativamente banalmente resa abbastanza economica. Di solito solo poche dozzine di parole devono essere copiate dalla memoria che si trova in un blocco contiguo quasi mai più di 1 MB di dimensione.La ricerca di gestori è allo stesso modo economica, ma la chiamata alle routine di filtro può causare l'estrazione di molti altri codici e se l'eccezione di dispatching e stack unwinding logic non è ottimizzata per il lancio di eccezioni frequenti, la configurazione dello stack frame ecc. Per ogni passaggio di l'intera operazione può sommarsi. –

+2

@Scott: Non vedo che abbia alcun senso distinguere tra il lancio e la cattura. Sono inestricabilmente collegati: non puoi averne uno senza l'altro, quindi non fa differenza quello che tu chiami. –

1

Lanciare un'eccezione è un'operazione relativamente economica. Li sta intercettando con il costo a causa delle sequenze di stack che devono verificarsi per trovare i gestori catch, eseguire il codice nei catch catcher, trovare i blocchi finali, eseguire il codice nei blocchi finally e quindi tornare al caller originale.

È fortemente consigliato per non utilizzare eccezioni per il controllo del flusso. L'uso di codici di ritorno per indicare errori diventa costoso da un punto di vista del "tempo e materiali" in quanto alla fine incorrerà in costi di manutenzione.

Tutto questo a parte, i due esempi non corrispondono né sono nemmeno validi. Dal momento che sono di ritorno b, che è di tipo Bar, il vostro primo metodo dovrebbe probabilmente essere:

public Bar Foo(Bar b) 
{ 
    if(b.Success) 
    { 
     return b; 
    } 
    else 
    { 
     throw n.Exception; 
    } 
} 

che potrebbe essere riscritta come:

public Bar Foo(Bar b) 
{ 
    if (!b.Success) 
     throw n.Exception; 

    return b; 
} 
1

In generale ho evitato questo a causa della natura costoso di cogliere l'eccezione. Potrebbe non essere troppo male se non è qualcosa che accade spesso.

Tuttavia, perché non restituire null? Allora diventa questo:

public Foo Bar(Bar b) 
{ 
    if(b.Success) 
    { 
     return b; 
    } 
    else 
    { 
     return null; 
    } 
} 

E poi ogni volta che si chiama Bar() è sufficiente verificare che il valore restituito non è null prima di utilizzare il valore. Questa è un'operazione molto meno costosa. E immagino sia una buona pratica, perché questa è la tecnica che Microsoft ha utilizzato in tutte le funzioni incorporate in .NET.

+0

Questa è una pessima pratica, a causa della possibilità che qualcuno dimenticherà di verificare la presenza di null. Inoltre, il codice viene nascosto con i controlli di qualcosa che è destinato ad accadere raramente, oscurando ciò che il codice è effettivamente destinato a fare. –

+0

"Questa è una pessima pratica, a causa della possibilità che qualcuno dimenticherà di verificare la presenza di null." - Non si potrebbe dire lo stesso per aver lanciato un'eccezione? Se lanci un'eccezione devi ricordarti di prenderla. –

+0

Credo che il mio punto sia che in entrambi i casi questo tornerà a morderti se non scrivi correttamente il codice che circonda la chiamata della funzione. La differenza è che il mio metodo è molto, molto più veloce di lanciare e catturare un'eccezione. –

19

usando il codice seguente, il test ha rivelato che la chiamata + tornare senza eccezioni sono voluti circa 1,6 microsecondi per ogni iterazione, mentre eccezioni (gettare più cattura) aggiunti i circa microsecondi ciascuno. (!)

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     DateTime start = DateTime.Now; 
     bool PreCheck = chkPrecheck.Checked; 
     bool NoThrow = chkNoThrow.Checked; 
     int divisor = (chkZero.Checked ? 0 : 1); 
     int Iterations = Convert.ToInt32(txtIterations.Text); 
     int i = 0; 
     ExceptionTest x = new ExceptionTest(); 
     int r = -2; 
     int stat = 0; 

     for(i=0; i < Iterations; i++) 
     { 
      try 
      { 
       r = x.TryDivide(divisor, PreCheck, NoThrow); 
      } 
      catch 
      { 
       stat = -3; 
      } 

     } 

     DateTime stop = DateTime.Now; 
     TimeSpan elapsed = stop - start; 
     txtTime.Text = elapsed.TotalMilliseconds.ToString(); 

     txtReturn.Text = r.ToString(); 
     txtStatus.Text = stat.ToString(); 

    } 
} 



class ExceptionTest 
{ 
    public int TryDivide(int quotient, bool precheck, bool nothrow) 
    { 
     if (precheck) 
     { 
      if (quotient == 0) 
      { 
       if (nothrow) 
       { 
        return -9; 
       } 
       else 
       { 
        throw new DivideByZeroException(); 
       } 

      } 
     } 
     else 
     { 
      try 
      { 
       int a; 
       a = 1/quotient; 
       return a; 
      } 
      catch 
      { 
       if (nothrow) 
       { 
        return -9; 
       } 
       else 
       { 
        throw; 
       } 
      } 
     } 
     return -1; 
    } 
} 

Quindi sì, le eccezioni sono MOLTO costoso.

E prima che qualcuno dice che, YES, ho provato questo uscita modalità e non solo Debug modalità. Prova tu stesso il codice e vedi se ottieni risultati significativamente diversi.

+5

+1: per testarlo effettivamente anziché solo pontificare. Perché i programmatori discutono su cose stupide che potrebbero semplicemente misurare? Mi fa arrabbiare, grrrr ... – user139593

+1

Grazie per aver pubblicato questi risultati. Sapevo che sarebbe stata una grande differenza. Ma questa è davvero una grande differenza. Roba buona. –

+1

Penso che questa dovrebbe essere la risposta accettata, perché fornisce un ragionamento misurabile – shabby

0

Ho visto molti sistemi diversi in cui le eccezioni erano considerate un'indicazione di un problema software e significa anche fornire informazioni sugli eventi che portano ad esso. Questo è sempre pesante sul sistema. Esistevano tuttavia sistemi in cui l'eccezione non era eccezionale in quanto il linguaggio stesso utilizzava metodi uguali o simili per tornare dalla routine ecc. Quindi si riduce a come le eccezioni sono incorporate nel linguaggio e nel sistema. Ho effettuato le mie misurazioni in java e in un sistema proprietario su cui lavoro in questo momento e i risultati erano come previsto - eccezionali, le eccezioni erano (20-25)% più costose.

Questa non deve essere la regola. Mi chiedo ad esempio come si poggia Python su questo. Non eseguo più lo sviluppo di python, quindi non ho intenzione di indagare.

Come una prova aneddotica forse interessante, prendere questo: nel sistema proprietario ho lavorato su alcuni anni indietro un uso non intelligente delle eccezioni per le violazioni del protocollo ha portato a problemi gravi e interruzione di uno dei nostri sistemi di produzione. Sono state utilizzate eccezioni per indicare campi obbligatori mancanti o corrotti in un messaggio ricevuto dall'esterno. Tutto andò bene fino a quando un giorno di sole una compagnia meno esperta produsse un nodo che, una volta messo in funzione, causò molti problemi. Le eccezioni dovevano essere rimosse poiché non siamo riusciti a far fronte anche a un livello basso di segnalazione da nodo difettoso. La colpa era in effetti anche dalla nostra parte - invece di seguire le specifiche del protocollo che descrivevano cosa fare con i messaggi formattati in modo errato (ignorare e superare il contatore degli errori di segnalazione) abbiamo cercato di rilevare e correggere l'errore del software nel nodo esterno. Se le eccezioni fossero meno costose, questo non sarebbe un grosso problema.

Infatti durante i test di sicurezza in questi giorni ho sempre un TC o pochi che fanno esattamente questo - generano un volume elevato di situazioni di violazione del protocollo. Se gli sviluppatori utilizzano eccezioni per gestirli, allora il sistema può essere messo in ginocchio abbastanza facilmente. Quando questa volta fallisce, inizierò a misurare di nuovo la differenza ....

Problemi correlati