Fa il JIT ottimizzare .NET nidificate dichiarazioni try / catch?
-
12-09-2019 - |
Domanda
Ho pensato a dichiarazioni try / catch nidificato e ho iniziato a pensare a quali condizioni, se del caso, il JIT può eseguire l'ottimizzazione o la semplificazione del IL compilato.
Per illustrare, considerare le seguenti rappresentazioni funzionalmente equivalente di un gestore di eccezioni.
// Nested try/catch
try
{
try
{
try
{
foo();
}
catch(ExceptionTypeA) { }
}
catch(ExceptionTypeB) { }
}
catch(ExceptionTypeC) { }
// Linear try/catch
try
{
foo();
}
catch(ExceptionTypeA) { }
catch(ExceptionTypeB) { }
catch(ExceptionTypeC) { }
Supponendo che non ci sono riferimenti variabili aggiuntive o chiamate di funzione all'interno dei stack frame del try nidificato, può JIT concludere che gli stack frame possono essere ridotti a all'esempio lineare?
Ora come circa il seguente esempio?
void Try<TException>(Action action)
{
try
{
action();
}
catch (TException) { }
}
void Main()
{
Try<ExceptionC>(Try<ExceptionB>(Try<ExceptionA>(foo)));
}
Non credo che ci sia alcun modo per il JIT inline invocazioni delegato, in modo da questo esempio non può essere ridotto a quello precedente. Tuttavia, in caso di foo()
lancio ExceptionC
, non questa soluzione esegue più povera rispetto all'esempio lineare? Ho il sospetto che v'è un costo aggiuntivo per abbattere gli stack frame dalle invocazioni delegato, anche se i dati aggiuntivi contenuti nei fotogrammi è minima.
Soluzione
Vale la pena notare che nel primo caso sono solo funzionalmente equivalente quando si sta facendo nulla all'interno del blocco catch. In caso contrario, considerare questo:
try
{
foo();
}
catch (IOException)
{
throw new ArgumentException(); // Bubbles up to caller
}
catch (ArgumentException)
{
Console.WriteLine("Caught");
}
vs
try
{
try
{
foo();
}
catch (IOException)
{
throw new ArgumentException(); // Caught by other handler
}
}
catch (ArgumentException)
{
Console.WriteLine("Caught");
}
Ora, in questo caso la differenza è evidente, ma se il blocco catch chiama un metodo arbitrario, come è il JIT scopo di sapere che cosa potrebbe essere gettato? Meglio essere prudenti.
Questo ci lascia con la possibilità di JIT eseguire ottimizzazioni per blocchi catch vuoti - una pratica che è fortemente sconsigliato, in primo luogo. Non voglio JIT di trascorrere del tempo cercando di rilevare codice cattivo e farlo correre molto leggermente più veloce -. Se davvero non c'è alcuna differenza di prestazioni in primo luogo
Altri suggerimenti
mia comprensione di try / catch / finally limitata rispetto alle prestazioni è che tali regioni sono trasparenti per l'esecuzione regolare di codice. Cioè, se il codice non genera alcuna eccezione per la cattura, quindi i try / catch / finally regioni hanno ZERO impatto sulle prestazioni l'esecuzione di codice.
Tuttavia, quando viene sollevata un'eccezione, il runtime inizia a camminare lo stack dal sito in cui è cresciuto, controllando le tabelle di metadati per verificare se il sito in questione è contenuta in uno degli altri blocchi try critici. Se ne viene trovata (e ha un blocco catch ammissibile o di una blocco finally) allora il gestore in questione viene identificato e rami di esecuzione a questo punto.
Il processo di raccolta e gestione delle eccezioni è costoso dal punto di vista delle prestazioni. I programmatori non dovrebbero utilizzare eccezioni come un modo di segnalare o controllare il flusso del programma in qualcosa di diverso da circostanze eccezionali (gioco di parole).