prova a cogliere le prestazioni

In questo articolo su MSDN si afferma che è ansible utilizzare tutti i blocchi di catch try che si desidera e non comportano alcun costo di prestazioni finché non viene generata alcuna eccezione reale.
Dal momento che ho sempre creduto che un try-catch richiedesse sempre un piccolo successo in termini di performance anche quando non si lanciava l’eccezione, ho fatto un piccolo test.

private void TryCatchPerformance() { int iterations = 100000000; Stopwatch stopwatch = Stopwatch.StartNew(); int c = 0; for (int i = 0; i < iterations; i++) { try { // c += i * (2 * (int)Math.Floor((double)i)); c += i * 2; } catch (Exception ex) { throw; } } stopwatch.Stop(); WriteLog(String.Format("With try catch: {0}", stopwatch.ElapsedMilliseconds)); Stopwatch stopwatch2 = Stopwatch.StartNew(); int c2 = 0; for (int i = 0; i < iterations; i++) { // c2 += i * (2 * (int)Math.Floor((double)i)); c2 += i * 2; } stopwatch2.Stop(); WriteLog(String.Format("Without try catch: {0}", stopwatch2.ElapsedMilliseconds)); } 

L’output ottengo:

 With try catch: 68 Without try catch: 34 

Quindi sembra che l’utilizzo di nessun blocco try-catch sembra essere più veloce, dopo tutto?

Quello che trovo ancora più strano è che quando sostituisco il calcolo nel corpo dei cicli for di qualcosa di più complesso come: c += i * (2 * (int)Math.Floor((double)i));
La differenza è molto meno drammatica.

 With try catch: 640 Without try catch: 655 

Sto facendo qualcosa di sbagliato qui o c’è una spiegazione logica per questo?

Il JIT non esegue l’ottimizzazione sui blocchi “protetti” / “provati” e credo che a seconda del codice che scrivi nei blocchi try / catch, ciò influirà sulle tue prestazioni.

Lo stesso blocco try / catch / finally / fault non ha essenzialmente overhead in un assembly di release ottimizzato. Mentre spesso viene aggiunta l’aggiunta di IL per la cattura e infine i blocchi, quando non viene lanciata alcuna eccezione, c’è poca differenza nel comportamento. Piuttosto che un semplice ret, di solito c’è un congedo per un secondo ret.

Il vero costo dei blocchi try / catch / finally si verifica quando si gestisce un’eccezione. In questi casi, è necessario creare un’eccezione, inserire indicatori di ricerca per indicizzazione e, se l’eccezione viene gestita e la proprietà StackTrace a cui si accede, si verifica una sovrapposizione. L’operazione più pesante è la traccia dello stack, che segue i segni di scansione di stack precedentemente impostati per creare un object StackTrace che può essere utilizzato per visualizzare la posizione in cui si è verificato l’errore e le chiamate che ha fatto gorgogliare.

Se non c’è un comportamento in un blocco try / catch, allora il costo extra di “leave to ret” rispetto a “ret” sarà dominante, e ovviamente ci sarà una differenza misurabile. Tuttavia, in qualsiasi altra situazione in cui vi è un qualche tipo di comportamento nella clausola try, il costo del blocco stesso sarà completamente negato.

Nota che ho solo Mono disponibile:

 // a.cs public class x { static void Main() { int x = 0; x += 5; return ; } } // b.cs public class x { static void Main() { int x = 0; try { x += 5; } catch (System.Exception) { throw; } return ; } } 

Smontandoli:

 // a.cs default void Main () cil managed { // Method begins at RVA 0x20f4 .entrypoint // Code size 7 (0x7) .maxstack 3 .locals init ( int32 V_0) IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: ldloc.0 IL_0003: ldc.i4.5 IL_0004: add IL_0005: stloc.0 IL_0006: ret } // end of method x::Main 

e

 // b.cs default void Main () cil managed { // Method begins at RVA 0x20f4 .entrypoint // Code size 20 (0x14) .maxstack 3 .locals init ( int32 V_0) IL_0000: ldc.i4.0 IL_0001: stloc.0 .try { // 0 IL_0002: ldloc.0 IL_0003: ldc.i4.5 IL_0004: add IL_0005: stloc.0 IL_0006: leave IL_0013 } // end .try 0 catch class [mscorlib]System.Exception { // 0 IL_000b: pop IL_000c: rethrow IL_000e: leave IL_0013 } // end handler 0 IL_0013: ret } // end of method x::Main 

La differenza principale che vedo è a.cs va dritta a ret su IL_0006 , mentre b.cs deve leave IL_0013 a IL_006 . La mia ipotesi migliore, con il mio esempio, è che il leave è un salto (relativamente) costoso quando viene compilato su un codice macchina – che può essere o non essere il caso, specialmente nel ciclo for. Vale a dire, il try-catch non ha costi generali inerenti, ma saltare sopra il pescato ha un costo, come ogni ramo condizionale.

il calcolo reale è così minimo che le misurazioni accurate sono molto difficili. Mi sembra che provare a catturare possa aggiungere una quantità molto piccola di tempo extra alla routine. Rischerei di indovinare, non sapendo nulla su come le eccezioni sono implementate in C #, che questo è principalmente solo l’inizializzazione dei percorsi di eccezione e forse solo un leggero carico sul JIT.

Per qualsiasi uso effettivo, il tempo speso per il calcolo sarà tale da sopraffare il tempo speso a giocherellare con il try-catch che il costo del try-catch può essere preso quasi allo zero.

Il problema è prima nel tuo codice di prova. hai usato il cronometro.Elapsed.Milliseconds che mostra solo la parte in millisecondi del tempo trascorso, usa TotalMilliseconds per ottenere l’intera parte …

Se non viene lanciata alcuna eccezione, la differenza è minima

Ma la vera domanda è “Devo controllare le eccezioni o lasciare che C # gestisca il lancio delle eccezioni?”

Chiaramente … gestisci da solo … Prova a eseguire questo:

 private void TryCatchPerformance() { int iterations = 10000; textBox1.Text = ""; Stopwatch stopwatch = Stopwatch.StartNew(); int c = 0; for (int i = 0; i < iterations; i++) { try { c += i / (i % 50); } catch (Exception) { } } stopwatch.Stop(); Debug.WriteLine(String.Format("With try catch: {0}", stopwatch.Elapsed.TotalSeconds)); Stopwatch stopwatch2 = Stopwatch.StartNew(); int c2 = 0; for (int i = 0; i < iterations; i++) { int iMod50 = (i%50); if(iMod50 > 0) c2 += i / iMod50; } stopwatch2.Stop(); Debug.WriteLine( String.Format("Without try catch: {0}", stopwatch2.Elapsed.TotalSeconds)); } 

Uscita: OBSOLETA: guarda sotto! Con try catch: 1.9938401

Senza try catch: 8.92E-05

Incredibile, solo 10000 oggetti, con 200 eccezioni.

CORREZIONE: Eseguo il mio codice su DEBUG e VS Scritto eccezione per la finestra di output. Questi sono i risultati del RILASCIO Molto meno sovraccarico, ma ancora 7.500% di miglioramento.

Con try catch: 0.0546915

Controllo solo: 0.0007294

Con try catch Lancio del mio stesso object Exception: 0.0265229

Vedere la discussione sull’implementazione try / catch per una discussione su come funzionano i blocchi try / catch e su come alcune implementazioni hanno un overhead elevato e alcune hanno un overhead zero, quando non si verificano eccezioni.

Una differenza di soli 34 millisecondi è inferiore al margine di errore per un test come questo.

Come hai notato, quando aumenti la durata del test, la differenza diminuisce e le prestazioni dei due set di codice sono effettivamente le stesse.

Quando faccio questo tipo di benchmark, cerco di ricorrere ad ogni sezione di codice per almeno 20 secondi, preferibilmente più a lungo, e idealmente per diverse ore.