Pergunta

Então, eu sei que try/catch adiciona alguma sobrecarga e, portanto, não é uma boa maneira de controlar o fluxo do processo, mas de onde vem essa sobrecarga e qual é o seu impacto real?

Foi útil?

Solução

Não sou especialista em implementações de linguagem (então considere isso com cautela), mas acho que um dos maiores custos é desenrolar a pilha e armazená-la para o rastreamento de pilha.Suspeito que isso aconteça apenas quando a exceção é lançada (mas não sei) e, se assim for, esse seria um custo oculto de tamanho decente toda vez que uma exceção fosse lançada ...então não é como se você estivesse simplesmente pulando de um lugar para outro no código, 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 caminho típico e esperado no programa).

Outras dicas

Três pontos a serem destacados aqui:

  • Em primeiro lugar, há pouca ou NENHUMA penalidade de desempenho em 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 ação quando uma exceção é lançada.

  • Quando uma exceção é lançada além das operações de desenrolamento de pilha, etc., que ocorrem e que 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.

  • Acredito que esta seja uma das razões pelas quais o conselho geral, se você pretende relançar a exceção, é apenas throw; em vez de lançar a exceção novamente ou construir uma nova, pois nesses casos todas as informações da pilha são reunidas, enquanto no lançamento simples todas são preservadas.

Você está perguntando sobre a sobrecarga de usar try/catch/finalmente quando as exceções não são lançadas ou 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 perdas adicionais de cache devido à exceção lançada ao acessar dados residentes que normalmente não estão no cache.
  • Você pode esperar falhas de página adicionais devido à exceção lançada ao acessar código não residente e dados que normalmente não estão no conjunto de trabalho do seu aplicativo.

    • por exemplo, lançar a exceção exigirá que o CLR encontre a localização dos blocos finalmente e catch com base no IP atual e no IP de retorno de cada quadro até que a exceção seja tratada mais o bloco de filtro.
    • custo adicional de construção e resolução de nomes para criar os quadros para fins de diagnóstico, incluindo leitura de metadados, etc.
    • ambos os itens acima normalmente acessam códigos e dados "frios", portanto, falhas graves de página são prováveis ​​se você tiver alguma pressão de memória:

      • o CLR tenta colocar código e dados que são usados ​​com pouca frequência longe dos dados que são usados ​​com frequência para melhorar a localidade, então isso funciona contra você porque você está forçando o frio a ficar quente.
      • o custo das falhas graves de página, se houver, diminuirá todo o resto.
  • As situações típicas de captura são muitas vezes profundas, portanto os efeitos acima tenderiam a ser ampliados (aumentando a probabilidade de falhas de página).

Quanto ao impacto real do custo, isso pode variar muito dependendo do que mais está acontecendo no seu código no momento.Jon Skeet tem um bom resumo aqui, com alguns links úteis.Tenho tendência a concordar com a afirmação dele de que se você chegar ao ponto em que as exceções prejudicam significativamente o seu desempenho, você terá problemas em termos de uso de exceções além do desempenho.

Na minha experiência, a maior sobrecarga é lançar uma exceção e tratá-la.Certa vez, trabalhei em um projeto onde um código semelhante ao seguinte foi usado para verificar se alguém tinha o direito de editar algum objeto.Este método HasRight() era usado em todos os lugares da camada de apresentação e era frequentemente 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 ficou mais cheio de dados de teste, isso levou a uma desaceleração muito visível ao abrir novos formulários, etc.

Então, refatorei-o para o seguinte, que - de acordo com medições rápidas e sujas posteriores - é cerca de 2 ordens de magnitude mais rápida:

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.

Sem mencionar que se estiver dentro de um método chamado com frequência, poderá afetar o comportamento geral do aplicativo.
Por exemplo, considero o uso de Int32.Parse uma prática ruim na maioria dos casos, pois gera exceções para algo que, de outra forma, pode ser facilmente detectado.

Então, para concluir tudo o que está escrito aqui:
1) Use blocos try..catch para detectar erros inesperados - quase nenhuma penalidade de desempenho.
2) Não use exceções para erros excluídos se puder evitá-lo.

Escrevi um artigo sobre isso há algum tempo porque muitas pessoas perguntaram sobre isso na época.Você pode encontrá-lo e o código de teste em http://www.blackwasp.co.uk/SpeedTestTryCatch.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, considere 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 que quando as coisas dão errado, a maioria dos desenvolvedores (e muitos usuários) reconhecem a pausa como uma exceção que está prestes a acontecer!A chave aqui é não usar o tratamento de exceções para operações normais.Como o nome sugere, eles são excepcionais e você deve fazer tudo o que puder para evitar que sejam lançados.Você não deve usá-los como parte do fluxo esperado de um programa que esteja funcionando corretamente.

Eu fiz um entrada do blog sobre esse assunto no ano passado.Confira.O resultado final é que quase não há custo para um bloco try se nenhuma exceção ocorrer - e no meu laptop, a exceção foi de cerca de 36μs.Isso pode ser menos do que você esperava, mas tenha em mente que esses resultados estão em uma pilha rasa.Além disso, as primeiras exceções são muito lentas.

É muito mais fácil escrever, depurar e manter código 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 local e aceitas em outro).Por ser mais fácil, o código será, em média, melhor escrito e com menos 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 de exceções do computador é insignificante em comparação e geralmente pequena em termos da capacidade do aplicativo de atender aos requisitos de desempenho do mundo real.

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!

  1. Desativa algumas otimizações automáticas (por design), e, em alguns casos, injeta depuração código, como você pode esperar de um ajuda de depuração.Sempre haverá pessoas que discordarão de mim neste ponto, mas a linguagem exige isso e a desmontagem mostra isso, então essas pessoas são pela definição do dicionário delirante.
  2. Pode impactar negativamente na manutenção. Na verdade, esta é a questão mais significativa aqui, mas como minha última resposta (que se concentrou quase inteiramente nela) foi excluída, tentarei focar na questão menos significativa (a micro-otimização) em oposição à questão mais significativa ( a macro-otimização).

O primeiro foi abordado em algumas postagens de blog de MVPs da Microsoft ao longo dos anos, e espero que você possa encontrá-los facilmente, mas o StackOverflow se preocupa muito sobre contente então vou fornecer links para alguns deles como enchimento evidência:

Há também esta resposta que mostra a diferença entre código desmontado com e sem uso try/catch.

Parece tão óbvio que há é uma sobrecarga que é claramente observável na geração de código, e essa sobrecarga parece até ser reconhecida por pessoas que a Microsoft valoriza!No entanto, eu sou, repetindo a internet...

Sim, existem dezenas de instruções MSIL extras para uma linha trivial de código, e isso nem cobre as otimizações desabilitadas, então tecnicamente é uma micro-otimização.


Publiquei uma resposta anos atrás que foi excluída porque se concentrava na produtividade dos programadores (a macro-otimização).

Isso é lamentável, pois nenhuma economia de alguns nanossegundos aqui e ali de tempo de CPU provavelmente compensará muitas horas acumuladas de otimização manual por humanos.Pelo 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 apenas compre um computador mais rápido?

Claramente, deveríamos estar otimizando nossas prioridades, não apenas nosso código!Na minha última resposta, baseei-me nas diferenças entre dois trechos de código.

Usando try/catch:

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

Não use 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, o que provavelmente desperdiçará seu tempo, se não for na criação de perfil/otimização (abordado acima), que provavelmente nem seria necessário se não fosse pelo try/catch problema, então ao percorrer o código-fonte ...Um deles tem quatro linhas extras de lixo padrão!

À medida que mais e mais campos são introduzidos em uma classe, todo esse lixo padronizado 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 por trás de alguma abstração caseira, mas...então podemos também evitar exceções (ou seja,usar Int.TryParse).

Este nem é um exemplo complexo;Já vi 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, de outra forma, seriam aplicadas automaticamente pelo compilador.Que melhor maneira de dar origem à teoria de que o compilador é lento, ao contrário de o compilador está fazendo exatamente o que foi dito para fazer?

Supondo que uma exceção seja lançada pelo referido construtor e algum bug seja acionado como resultado, o desenvolvedor de manutenção deficiente terá que rastreá-lo.Isso pode não ser uma tarefa tão fácil, pois, ao contrário do código esparguete do Vá para pesadelo, try/catch pode causar bagunça 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, todos os quais serão observados pelo desenvolvedor de manutenção, o jeito difícil!No entanto, dizem-nos que “goto é perigoso”, heh!

No final menciono, try/catch tem seu benefício que é, foi projetado para desativar otimizações!É, se você quiser, um ajuda de depuração!Foi para isso que foi concebido e é para isso que deve ser utilizado...

Acho que isso também é um ponto positivo.Ele pode ser usado para desabilitar otimizações que poderiam prejudicar algoritmos de passagem de mensagens seguros e sensatos para aplicativos multithread e para capturar possíveis condições de corrida;) Esse é o único cenário em que consigo pensar para usar try/catch.Mesmo isso tem alternativas.


O que as otimizações fazem try, catch e finally desabilitar?

Também conhecido como

Como está try, catch e finally útil como auxiliar de depuração?

eles são barreiras de gravação.Isso vem do padrão:

12.3.3.13 Instruções try-catch

Para uma declaração stmt do formulário:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
  • O estado de atribuição definido de v no começo de bloco de tentativa é o mesmo que o estado de atribuição definido de v no começo de stmt.
  • O estado de atribuição definido de v no começo de pegar-bloco-i (para qualquer eu) é o mesmo que o estado de atribuição definido de v no começo de stmt.
  • O estado de atribuição definido de v no ponto final de stmt é definitivamente atribuído se (e somente se) v é definitivamente atribuído no ponto final de bloco de tentativa e cada pegar-bloco-i (para cada eu de 1 a n).

Em outras palavras, no início de cada try declaração:

  • todas as atribuições feitas a objetos visíveis antes de entrar no try A instrução deve ser completa, o que requer um bloqueio de thread para iniciar, tornando-a útil para depurar condições de corrida!
  • o compilador não tem permissão para:
    • eliminar atribuições de variáveis ​​não utilizadas que foram definitivamente atribuídas antes do try declaração
    • reorganizar ou unir qualquer um deles atribuições internas (ou seja,veja meu primeiro link, se ainda não o fez).
    • içar atribuições sobre esta barreira, para atrasar a atribuição a uma variável que ele sabe que não será usada até mais tarde (se for) ou para mover preventivamente atribuições posteriores para tornar possíveis outras otimizações...

Uma história semelhante vale para cada catch declaração;suponha que dentro do seu try instrução (ou um construtor ou função que ela invoca, etc.) você atribui a essa variável que de outra forma seria inútil (digamos, garbage=42;), o compilador não pode eliminar essa afirmação, por mais irrelevante que seja para o comportamento observável do programa.A tarefa precisa ter concluído antes de o catch bloco é inserido.

Pelo que vale a pena, finally diz da mesma forma degradante história:

12.3.3.14 Declarações Try-finalmente

Para tentar declaração stmt do formulário:

try try-block
finally finally-block

• O estado de atribuição definido de v no começo de bloco de tentativa é o mesmo que o estado de atribuição definido de v no começo de stmt.
• O estado de atribuição definido de v no começo de finalmente bloquear é o mesmo que o estado de atribuição definido de v no começo de stmt.
• O estado de atribuição definido de v no ponto final de stmt é definitivamente atribuído se (e somente se):ó v é definitivamente atribuído no ponto final de bloco de tentativaó v é definitivamente atribuído no ponto final de finalmente bloquearSe uma transferência de fluxo de controle (como uma Vá para declaração) é feita que começa dentro bloco de tentativa, e termina fora de bloco de tentativa, então v também é considerado definitivamente atribuído nessa transferência de fluxo de controle se v é definitivamente atribuído no ponto final de finalmente bloquear.(Isto não é apenas se—se v for definitivamente atribuído por outro motivo nesta transferência de fluxo de controle, então ainda será considerado definitivamente atribuído.)

12.3.3.15 Declarações try-catch-finally

Análise de atribuição definitiva para um tentar-pegar-finalmente declaração do formulário:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
finally finally-block

é feito como se a declaração fosse uma tentar-finalmente declaração que inclui um tentar-pegar declaração:

try { 
    try   
    try-block
    catch ( ... ) catch-block-1
    ...   
    catch ( ... ) catch-block-n
} 
finally finally-block

Eu realmente gosto do Hafthor postagem no blog, e para acrescentar minha opinião a esta discussão, gostaria de dizer que sempre foi fácil para mim fazer com que o DATA LAYER lançasse apenas um tipo de exceção (DataAccessException).Dessa forma, minha CAMADA DE NEGÓCIO sabe qual exceção esperar e a captura.Então, dependendo de outras regras de negócios (ou seja,se meu objeto de negócios participar do fluxo de trabalho, etc.), posso lançar uma nova exceção (BusinessObjectException) ou prosseguir sem lançar novamente.

Eu diria que não hesite em usar try..catch sempre que for necessário e use-o 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 atuais não adicionam nenhum overhead em casos comuns, 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 excepções são excepções e este 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 assembly, que serão executadas toda vez que você entrar em suas sub-rotinas.

Desculpe pelo meu inglês, espero que ajude você.Estas informações são baseadas no livro citado. Para obter 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 esses dois pedaços de código equivalentes têm quase exatamente o mesmo comprimento em bytes.Este último contém 4 bytes a menos de recuo.Isso é uma coisa ruim?

Para piorar a situação, um aluno decide fazer um loop enquanto a entrada pode ser analisada como um int.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
{
    ...
}

Blocos try/catch são formas mágicas de desperdiçar indentação, e ainda nem sabemos o motivo da falha!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 e agradável.Blocos Try/catch são validação/saneamento de dados de um preguiçoso.

Um dos custos menores é que os blocos try/catch de fato desativam 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 poderiam prejudicar algoritmos de passagem de mensagens seguros e sensatos para aplicativos multithread e para capturar possíveis condições de corrida;) Esse é o único cenário em que consigo pensar para usar try/catch.Mesmo isso tem alternativas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top