Respostas:
Não sou um especialista em implementações de linguagem (portanto, considere isso com cautela), mas acho que um dos maiores custos é desfazer a pilha e armazená-la para o rastreamento de pilha. Suspeito que isso aconteça apenas quando a exceção é lançada (mas eu não sei), e se sim, isso seria um custo oculto de tamanho decente toda vez que uma exceção é lançada ... então não é como se você estivesse apenas pulando de um lugar no código para outro, há muita coisa acontecendo.
Não acho que seja um problema, desde que você esteja usando exceções para comportamento EXCEPCIONAL (portanto, não é o seu caminho típico e esperado através do programa).
Três pontos a serem destacados aqui:
Em primeiro lugar, há pouca ou nenhuma penalidade de desempenho em realmente ter blocos try-catch em seu código. Isso não deve ser levado em consideração ao tentar evitar tê-los em seu aplicativo. O impacto no desempenho só entra em jogo quando uma exceção é lançada.
Quando uma exceção é lançada além das operações de desenrolamento da pilha etc que ocorrem, as quais outros mencionaram, você deve estar ciente de que um monte de coisas relacionadas ao tempo de execução / reflexão acontecem para preencher os membros da classe de exceção, como o rastreamento de pilha objeto e os vários membros de tipo etc.
Eu acredito que esta é uma das razões pelas quais o conselho geral, se você for relançar a exceção, é apenas, em throw;
vez de lançar a exceção novamente ou construir uma nova, já que nesses casos todas as informações da pilha são reunidas, enquanto no simples jogue é tudo preservado.
throw new Exception("Wrapping layer’s error", ex);
Você está perguntando sobre a sobrecarga de usar try / catch / finally quando as exceções não são lançadas ou sobre a sobrecarga de usar exceções para controlar o fluxo do processo? Este último é semelhante a usar uma banana de dinamite para acender a vela de aniversário de uma criança, e a sobrecarga associada cai nas seguintes áreas:
Você pode esperar falhas de página adicionais devido à exceção lançada acessando código não residente e dados que normalmente não estão no conjunto de trabalho do seu aplicativo.
ambos os itens acima normalmente acessam código e dados "frios", então as falhas de página são prováveis se você tiver pressão de memória:
Quanto ao impacto real do custo, isso pode variar muito dependendo do que mais está acontecendo em seu código no momento. Jon Skeet tem um bom resumo aqui , com alguns links úteis. Eu tendo a concordar com sua afirmação de que se você chegar ao ponto em que as exceções estão prejudicando significativamente o seu desempenho, você terá problemas em termos de uso de exceções além do desempenho apenas.
Na minha experiência, a maior sobrecarga é realmente lançar uma exceção e tratá-la. Certa vez, trabalhei em um projeto em que um código semelhante ao seguinte era usado para verificar se alguém tinha o direito de editar algum objeto. Este método HasRight () era usado em todos os lugares na camada de apresentação e era freqüentemente chamado para centenas de objetos.
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 o banco de dados de teste fica mais cheio de dados de teste, isso leva a uma desaceleração muito visível ao abrir novos formulários, etc.
Então, eu refatorei para o seguinte, que - de acordo com medições rápidas e sujas posteriores - é cerca de 2 ordens de magnitude mais rápido:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Resumindo, usar exceções no fluxo de processo normal é cerca de duas ordens de magnitude mais lento do que usar um fluxo de processo semelhante sem exceções.
Ao contrário das teorias comumente aceitas, try
/ catch
pode ter implicações significativas no desempenho, independentemente de uma exceção ser lançada ou não!
O primeiro foi abordado em algumas postagens de blog de MVPs da Microsoft ao longo dos anos, e acredito que você possa encontrá-los facilmente, mas o StackOverflow se preocupa muito com o conteúdo, então vou fornecer links para alguns deles como prova de preenchimento :
try
/ catch
/finally
( e parte dois ), por Peter Ritchie explora as otimizações quetry
/catch
/finally
desativa (e irei mais adiante com citações do padrão)Parse
vs. TryParse
vs.ConvertTo
por Ian Huff afirma abertamente que "o tratamento de exceções é muito lento" e demonstra este ponto por picadaInt.Parse
eInt.TryParse
um contra o outro ... Para qualquer um que insiste queTryParse
os usostry
/catch
nos bastidores, isso deve lançar alguma luz!Há também esta resposta que mostra a diferença entre código desmontado com e sem usar try
/catch
.
Parece tão óbvio que não é uma sobrecarga que é descaradamente observável na geração de código, e que a sobrecarga parece mesmo ser reconhecido por pessoas que valor Microsoft! Ainda estou, repetindo a internet ...
Sim, existem dezenas de instruções MSIL extras para uma linha de código trivial e isso nem mesmo cobre as otimizações desabilitadas, então tecnicamente é uma micro-otimização.
Eu postei uma resposta anos atrás que foi excluída por se concentrar na produtividade dos programadores (a macro-otimização).
Isso é lamentável, pois nenhuma economia de alguns nanossegundos aqui e ali no tempo da CPU provavelmente compensará muitas horas acumuladas de otimização manual por humanos. O que seu chefe paga mais: uma hora do seu tempo ou uma hora com o computador ligado? Em que ponto desligamos a tomada e admitimos que é hora de comprar um computador mais rápido ?
Obviamente, devemos otimizar nossas prioridades , não apenas nosso código! Em minha última resposta, usei as diferenças entre dois fragmentos de código.
Usando try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Não usando try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Considere da perspectiva de um desenvolvedor de manutenção, que é mais provável que perca seu tempo, se não no perfil / otimização (coberto acima), o que provavelmente nem seria necessário se não fosse pelo problema try
/ catch
, então rolar através código-fonte ... Um deles tem quatro linhas extras de lixo clichê!
À medida que mais e mais campos são introduzidos em uma classe, todo esse lixo clichê se acumula (tanto no código-fonte quanto no código desmontado) muito além dos níveis razoáveis. Quatro linhas extras por campo, e são sempre as mesmas linhas ... Não fomos ensinados a evitar a repetição? Suponho que poderíamos esconder o try
/ catch
atrás de alguma abstração caseira, mas ... então podemos também evitar exceções (ou seja, usarInt.TryParse
).
Este não é nem mesmo um exemplo complexo; Tenho visto tentativas de instanciar novas classes em try
/ catch
. Considere que todo o código dentro do construtor pode então ser desqualificado de certas otimizações que seriam aplicadas automaticamente pelo compilador. Qual a melhor maneira de dar origem à teoria de que o compilador é lento , ao contrário de que o compilador está fazendo exatamente o que foi mandado ?
Supondo que uma exceção seja lançada pelo referido construtor, e algum bug seja acionado como resultado, o desenvolvedor de manutenção deficiente deve rastreá-lo. Isso pode não ser uma tarefa tão fácil, pois ao contrário do código espaguete do pesadelo goto , try
/ catch
pode causar bagunças em três dimensões , já que poderia subir na pilha não apenas para outras partes do mesmo método, mas também para outras classes e métodos , tudo isso será observado pelo desenvolvedor da manutenção, da maneira mais difícil ! No entanto, somos informados de que "goto é perigoso", heh!
No final, menciono que try
/ catch
tem sua vantagem, que é projetada para desativar otimizações ! É, se você quiser, um auxiliar de depuração ! É para isso que foi projetado e é para isso que deve ser usado ...
Acho que isso também é um ponto positivo. Ele pode ser usado para desabilitar otimizações que podem, de outra forma, paralisar algoritmos de transmissão de mensagens sãos e seguros para aplicativos multithread e para capturar possíveis condições de corrida;) Esse é o único cenário que consigo pensar para usar try / catch. Mesmo isso tem alternativas.
O que otimizações fazer try
, catch
e finally
desativar?
AKA
Como são try
, catch
e finally
útil como depuração ajudas?
eles são barreiras de gravação. Isso vem do padrão:
12.3.3.13 Declarações try-catch
Para obter uma declaração stmt da forma:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- O estado de atribuição definida de v no início do bloco try é o mesmo que o estado de atribuição definida de v no início de stmt .
- O estado de atribuição definido de v no início de catch-block-i (para qualquer i ) é o mesmo que o estado de atribuição definido de v no início de stmt .
- O estado de atribuição definido de v no ponto final de stmt é definitivamente atribuído se (e somente se) v for definitivamente atribuído no ponto final do bloco try e cada catch-block-i (para cada i de 1 a n )
Em outras palavras, no início de cada try
afirmação:
try
instrução devem ser concluídas, o que requer um bloqueio de thread para iniciar, tornando-o útil para depurar condições de corrida!try
declaraçãoUma história semelhante se aplica a cada catch
afirmação; suponha que dentro de sua try
instrução (ou um construtor ou função que ele invoca, etc) você atribua a essa variável sem sentido (digamos, garbage=42;
), o compilador não pode eliminar essa instrução, não importa o quão irrelevante seja para o comportamento observável do programa . A atribuição precisa ser concluída antes que o catch
bloco seja inserido.
Por que vale a pena, finally
conta uma história igualmente degradante :
12.3.3.14 Declarações try-finally
Para uma declaração try stmt do formulário:
try try-block finally finally-block
• O estado de atribuição definida de v no início do bloco try é o mesmo que o estado de atribuição definida de v no início de stmt .
• O estado de atribuição definida de v no início do bloco final é o mesmo que o estado de atribuição definida de v no início de stmt .
• O estado de atribuição definido de v no ponto final de stmt é definitivamente atribuído se (e somente se): o v é definitivamente atribuído no ponto final do bloco try o vé definitivamente atribuído no ponto final do bloco final Se uma transferência de fluxo de controle (como uma instrução goto ) é feita que começa dentro do bloco try , e termina fora do bloco try , então v também é considerado definitivamente atribuído naquele controlar a transferência de fluxo se v for definitivamente atribuído no ponto final do bloco final . (Este não é um somente se - se v for definitivamente atribuído por outro motivo nesta transferência de fluxo de controle, então ainda é considerado definitivamente atribuído.)
12.3.3.15 Instruções try-catch-finally
Análise de atribuição definida para uma declaração try - catch - finally do formulário:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
é feito como se a instrução fosse uma instrução try - finally envolvendo uma instrução try - catch :
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Sem falar que se estiver dentro de um método frequentemente chamado, pode afetar o comportamento geral da aplicação.
Por exemplo, considero o uso de Int32.Parse uma má prática na maioria dos casos, uma vez que lança exceções para algo que pode ser facilmente detectado de outra forma.
Portanto, para concluir tudo o que está escrito aqui:
1) Use os blocos try..catch para detectar erros inesperados - quase nenhuma penalidade de desempenho.
2) Não use exceções para erros de exceção, se puder evitá-los.
Eu escrevi um artigo sobre isso um tempo atrás porque havia muitas pessoas perguntando sobre isso na época. Você pode encontrá-lo e o código de teste em http://www.blackwasp.co.uk/SpeedTestExperimenteCatch.aspx .
O resultado é que há uma pequena quantidade de sobrecarga para um bloco try / catch, mas tão pequena que deve ser ignorada. No entanto, se você estiver executando blocos try / catch em loops que são executados milhões de vezes, você pode querer considerar mover o bloco para fora do loop, se possível.
O principal problema de desempenho com blocos try / catch é quando você realmente captura uma exceção. Isso pode adicionar um atraso perceptível ao seu aplicativo. Claro, quando as coisas estão dando errado, a maioria dos desenvolvedores (e muitos usuários) reconhece a pausa como uma exceção que está prestes a acontecer! A chave aqui é não usar tratamento de exceção para operações normais. Como o nome sugere, eles são excepcionais e você deve fazer todo o possível para evitar que sejam atirados. Você não deve usá-los como parte do fluxo esperado de um programa que está funcionando corretamente.
Eu fiz uma entrada no blog sobre esse assunto no ano passado. Confira. O ponto principal é que quase não há custo para um bloco try se nenhuma exceção ocorrer - e no meu laptop, uma exceção era de cerca de 36 μs. Isso pode ser menos do que você esperava, mas tenha em mente que esses resultados foram em uma pilha superficial. Além disso, as primeiras exceções são muito lentas.
try
/ catch
demais? Heh heh), mas parece que você está discutindo com a especificação do idioma e alguns MS MVPs que também escreveram blogs sobre o assunto, fornecendo medidas ao contrário do seu conselho ... Estou aberto à sugestão de que a pesquisa que fiz está errada, mas vou precisar ler a entrada do seu blog para ver o que diz.
try-catch
block vs tryparse()
, mas o conceito é o mesmo.
É muito mais fácil escrever, depurar e manter o código que está livre de mensagens de erro do compilador, mensagens de aviso de análise de código e exceções aceitas de rotina (particularmente exceções que são lançadas em um lugar e aceitas em outro). Por ser mais fácil, o código será, em média, melhor escrito e menos cheio de erros.
Para mim, essa sobrecarga do programador e da qualidade é o principal argumento contra o uso de try-catch para fluxo de processo.
A sobrecarga do computador de exceções é insignificante em comparação e geralmente pequena em termos da capacidade do aplicativo de atender aos requisitos de desempenho do mundo real.
Eu realmente gosto da postagem do blog de Hafthor , e para adicionar meus dois centavos a esta discussão, eu gostaria de dizer que sempre foi fácil para mim fazer com que a CAMADA DE DADOS lance apenas um tipo de exceção (DataAccessException). Dessa forma, minha CAMADA DE NEGÓCIOS sabe que exceção esperar e a captura. Então, dependendo de outras regras de negócios (ou seja, se meu objeto de negócios participa do fluxo de trabalho, etc.), posso lançar uma nova exceção (BusinessObjectException) ou prosseguir sem relançar / lançar.
Eu diria que não hesite em usar try..catch sempre que for necessário e usá-lo com sabedoria!
Por exemplo, este método participa de um fluxo de trabalho ...
Comentários?
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;
}
Podemos ler em Programming Languages Pragmatics de Michael L. Scott que os compiladores de hoje em dia não adicionam nenhum overhead no caso comum, ou seja, quando não ocorrem exceções. Portanto, todo trabalho é feito em tempo de compilação. Mas quando uma exceção é lançada em tempo de execução, o compilador precisa realizar uma pesquisa binária para encontrar a exceção correta e isso acontecerá para cada novo lançamento que você fizer.
Mas exceções são exceções e esse custo é perfeitamente aceitável. Se você tentar fazer o Tratamento de Exceções sem exceções e usar códigos de erro de retorno, provavelmente precisará de uma instrução if para cada sub-rotina e isso resultará em uma sobrecarga em tempo real. Você sabe que uma instrução if é convertida em algumas instruções de montagem, que serão executadas toda vez que você entrar em suas sub-rotinas.
Desculpe pelo meu inglês, espero que ajude você. Esta informação é baseada no livro citado, para mais informações consulte o Capítulo 8.5 Tratamento de Exceções.
Vamos analisar um dos maiores custos possíveis de um bloco try / catch quando usado onde não deveria ser usado:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
E aqui está aquele sem try / catch:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Sem contar o insignificante espaço em branco, pode-se notar que essas duas partes equivalentes de código têm quase exatamente o mesmo comprimento em bytes. O último contém 4 bytes menos indentação. Isso é uma coisa ruim?
Para piorar a situação, o aluno decide fazer um loop enquanto a entrada pode ser analisada como um inteiro. A solução sem try / catch pode ser algo como:
while (int.TryParse(...))
{
...
}
Mas como isso fica ao usar try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Os blocos Try / catch são maneiras mágicas de desperdiçar indentação, e ainda nem sabemos o motivo do fracasso! Imagine como a pessoa que está fazendo a depuração se sente, quando o código continua a ser executado após uma falha lógica séria, em vez de parar com um erro de exceção óbvio. Os blocos Try / catch são validação / saneamento de dados de um preguiçoso.
Um dos custos menores é que os blocos try / catch realmente desabilitam certas otimizações: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx . Acho que isso também é um ponto positivo. Ele pode ser usado para desabilitar otimizações que podem, de outra forma, paralisar algoritmos de transmissão de mensagens sãos e seguros para aplicativos multithread e para capturar possíveis condições de corrida;) Esse é o único cenário que consigo pensar para usar try / catch. Mesmo isso tem alternativas.
Int.Parse
favor de Int.TryParse
.