Quanto sono costose le eccezioni in C #?

Quanto sono costose le eccezioni in C #? Sembra che non siano incredibilmente costosi fintanto che lo stack non è profondo; tuttavia ho letto rapporti contraddittori.

C’è una relazione definitiva che non è stata confutata?

Jon Skeet ha scritto Exceptions and Performance in .NET nel gennaio 2006

Quale è stato aggiornato Eccezioni e Performance Redux (grazie a @Gulzar)

A cui Rico Mariani ha sintetizzato The True Cost of .NET Exceptions – Solution


Anche riferimento: Krzysztof Cwalina – Aggiornamento delle linee guida di progettazione: lancio di eccezioni

Immagino di essere nel campo che se l’esecuzione delle eccezioni ha un impatto sulla tua applicazione, allora ne stai gettando troppe. Le eccezioni dovrebbero essere per condizioni eccezionali, non come gestione di errori di routine.

Detto questo, il mio ricordo di come vengono gestite le eccezioni sta essenzialmente risalendo la pila trovando un’istruzione catch che corrisponde al tipo di eccezione generata. Quindi le prestazioni saranno influenzate maggiormente dalla profondità del pescato e da quante dichiarazioni di cattura hai.

Avendo letto che le eccezioni sono costose in termini di prestazioni, ho messo insieme un semplice programma di misurazione, molto simile a quello pubblicato da Jon Skeet anni fa . Lo dico qui principalmente per fornire numeri aggiornati.

Il programma ha impiegato meno di 29914 millisecondi per elaborare un milione di eccezioni, il che equivale a 33 eccezioni al millisecondo . È abbastanza veloce da rendere le eccezioni un’alternativa praticabile ai codici di ritorno per la maggior parte delle situazioni.

Si noti, tuttavia, che con i codici di ritorno anziché le eccezioni, lo stesso programma viene eseguito meno di un millisecondo, il che significa che le eccezioni sono almeno 30.000 volte più lente dei codici di ritorno . Come sottolineato da Rico Mariani, questi numeri sono anche numeri minimi. In pratica, lanciare e catturare un’eccezione richiederà più tempo.

Misurato su un laptop con Intel Core2 Duo T8100 a 2,1 GHz con .NET 4.0 in release build non eseguito con debugger (che potrebbe rallentare notevolmente).

Questo è il mio codice di prova:

static void Main(string[] args) { int iterations = 1000000; Console.WriteLine("Starting " + iterations.ToString() + " iterations...\n"); var stopwatch = new Stopwatch(); // Test exceptions stopwatch.Reset(); stopwatch.Start(); for (int i = 1; i < = iterations; i++) { try { TestExceptions(); } catch (Exception) { // Do nothing } } stopwatch.Stop(); Console.WriteLine("Exceptions: " + stopwatch.ElapsedMilliseconds.ToString() + " ms"); // Test return codes stopwatch.Reset(); stopwatch.Start(); int retcode; for (int i = 1; i <= iterations; i++) { retcode = TestReturnCodes(); if (retcode == 1) { // Do nothing } } stopwatch.Stop(); Console.WriteLine("Return codes: " + stopwatch.ElapsedMilliseconds.ToString() + " ms"); Console.WriteLine("\nFinished."); Console.ReadKey(); } static void TestExceptions() { throw new Exception("Failed"); } static int TestReturnCodes() { return 1; } 

Gli oggetti eccezione Barebone in C # sono abbastanza leggeri; di solito è la capacità di incapsulare una InnerException che lo rende pesante quando l’albero degli oggetti diventa troppo profondo.

Per quanto riguarda un rapporto definitivo, non ne sono a conoscenza, anche se un profilo punttrace punttrace (o qualsiasi altro profiler) per il consumo di memoria e la velocità sarà abbastanza facile da fare.

Nel mio caso, le eccezioni erano molto costose. Ho riscritto questo:

 public BlockTemplate this[int x,int y, int z] { get { try { return Data.BlockTemplate[World[Center.X + x, Center.Y + y, Center.Z + z]]; } catch(IndexOutOfRangeException e) { return Data.BlockTemplate[BlockType.Air]; } } } 

In questo:

 public BlockTemplate this[int x,int y, int z] { get { int ix = Center.X + x; int iy = Center.Y + y; int iz = Center.Z + z; if (ix < 0 || ix >= World.GetLength(0) || iy < 0 || iy >= World.GetLength(1) || iz < 0 || iz >= World.GetLength(2)) return Data.BlockTemplate[BlockType.Air]; return Data.BlockTemplate[World[ix, iy, iz]]; } } 

E notato un buon aumento di velocità di circa 30 secondi. Questa funzione viene chiamata almeno 32K volte all’avvio. Il codice non è chiaro quanto all’intenzione, ma i risparmi sono enormi.

La performance colpita con le eccezioni sembra essere al punto di generare l’object eccezione (anche se troppo piccolo per causare preoccupazioni il 90% delle volte). Pertanto, la raccomandazione è di profilare il codice: se le eccezioni causano un calo di prestazioni, si scrive un nuovo metodo high-perf che non utilizza eccezioni. (Un esempio che viene in mente sarebbe (TryParse introdotto per superare i problemi di perf con Parse che utilizza le eccezioni)

Detto questo, le eccezioni nella maggior parte dei casi non causano risultati significativi nelle prestazioni nella maggior parte delle situazioni – quindi la MS Design Guideline deve segnalare i fallimenti generando eccezioni

Ho fatto le mie misurazioni per scoprire quanto seria sia l’implicazione delle eccezioni. Non ho provato a misurare il tempo assoluto per l’eccezione di lancio / cattura. Quello che mi interessava di più è quanto più lento diventerà un ciclo se viene lanciata un’eccezione in ogni passaggio. Il codice di misurazione è simile a questo

  for(; ; ) { iValue = Level1(iValue); lCounter += 1; if(DateTime.Now >= sFinish) break; } 

vs

  for(; ; ) { try { iValue = Level3Throw(iValue); } catch(InvalidOperationException) { iValue += 3; } lCounter += 1; if(DateTime.Now >= sFinish) break; } 

La differenza è 20 volte Il secondo frammento è 20 volte più lento.