Durante uma revisão de código com um funcionário da Microsoft, encontramos uma grande seção de código dentro de um try{}
bloco. Ela e um representante de TI sugeriram que isso pode ter efeitos no desempenho do código. De fato, eles sugeriram que a maior parte do código deveria estar fora dos blocos try / catch e que somente seções importantes deveriam ser verificadas. O funcionário da Microsoft acrescentou e disse que um próximo informe técnico adverte contra blocos de tentativa / captura incorretos.
Eu olhei em volta e descobri que isso pode afetar as otimizações , mas parece se aplicar apenas quando uma variável é compartilhada entre escopos.
Não estou perguntando sobre a manutenção do código, ou mesmo sobre as exceções corretas (o código em questão precisa ser refatorado, sem dúvida). Também não estou me referindo ao uso de exceções para controle de fluxo, isso é claramente errado na maioria dos casos. Essas são questões importantes (algumas são mais importantes), mas não o foco aqui.
Como os blocos try / catch afetam o desempenho quando exceções não são lançadas?
fonte
Respostas:
Verifique-o.
Resultado:
Em milissegundos:
Novo Código:
Novos resultados:
fonte
Depois de ver todas as estatísticas de try / catch e sem try / catch, a curiosidade me forçou a olhar para trás para ver o que é gerado para os dois casos. Aqui está o código:
C #:
MSIL:
C #:
MSIL:
Eu não sou especialista em IL, mas podemos ver que um objeto de exceção local é criado na quarta linha
.locals init ([0] class [mscorlib]System.Exception ex)
depois que as coisas são praticamente as mesmas do método sem try / catch até a linha dezesseteIL_0021: leave.s IL_002f
. Se ocorrer uma exceção, o controle saltará para a linha,IL_0025: ldloc.0
caso contrário, saltaremos para o rótuloIL_002d: leave.s IL_002f
e a função retorna.Posso assumir com segurança que, se nenhuma exceção ocorrer, será a sobrecarga da criação de variáveis locais para armazenar
apenasobjetos de exceção e uma instrução de salto.fonte
Não. Se as otimizações triviais que um bloco try / finalmente impedem realmente têm um impacto mensurável em seu programa, você provavelmente não deve usar o .NET em primeiro lugar.
fonte
Explicação bastante abrangente do modelo de exceção .NET.
Petiscos de desempenho de Rico Mariani: Custo de exceção: Quando jogar e quando não
Dmitriy Zaslavskiy:
fonte
A estrutura é diferente, no exemplo de Ben M . Ele será estendido acima do
for
loop interno, o que fará com que não seja uma boa comparação entre os dois casos.A seguir, é mais preciso para comparação, onde todo o código a ser verificado (incluindo declaração de variável) está dentro do bloco Try / Catch:
Quando executei o código de teste original de Ben M , notei uma diferença nas configurações Debug e Releas.
Nesta versão, notei uma diferença na versão de depuração (na verdade mais do que a outra versão), mas não houve diferença na versão de lançamento.
Conclution :
Com base nestes testes, acho que podemos dizer que try / catch faz ter um pequeno impacto no desempenho.
EDIT:
Tentei aumentar o valor do loop de 10000000 para 1000000000 e executei novamente no Release para obter algumas diferenças no release, e o resultado foi o seguinte:
Você vê que o resultado é inconseqüente. Em alguns casos, a versão usando Try / Catch é realmente mais rápida!
fonte
try/catch
aqui. Você está cronometrando 12 tentativas / capturas na seção crítica contra loops de 10 milhões. O ruído do loop irá erradicar qualquer influência que o try / catch tenha. se, em vez disso, você colocar o try / catch dentro do loop apertado e comparar com / sem, você acabaria com o custo do try / catch. (sem dúvida, essa codificação geralmente não é uma boa prática, mas se você deseja calcular a sobrecarga de uma construção, é assim que a faz). Atualmente, o BenchmarkDotNet é a ferramenta essencial para tempos de execução confiáveis.Testei o impacto real de um
try..catch
circuito fechado e ele é pequeno demais para ser uma preocupação de desempenho em qualquer situação normal.Se o loop fizer muito pouco trabalho (no meu teste que fiz
x++
), você poderá medir o impacto do tratamento de exceções. O loop com manipulação de exceção demorou cerca de dez vezes mais para ser executado.Se o loop executar algum trabalho real (no meu teste, chamei o método Int32.Parse), o tratamento de exceções terá muito pouco impacto para ser mensurável. Eu tenho uma diferença muito maior trocando a ordem dos loops ...
fonte
Os blocos try catch têm um impacto insignificante no desempenho, mas a exceção Arremesso pode ser bastante considerável, provavelmente é aí que seu colega de trabalho ficou confuso.
fonte
A tentativa / captura tem impacto no desempenho.
Mas não é um grande impacto. a complexidade try / catch é geralmente O (1), como uma atribuição simples, exceto quando são colocadas em um loop. Então você tem que usá-los com sabedoria.
Aqui está uma referência sobre o desempenho try / catch (embora não explique a complexidade, mas está implícito). Dê uma olhada na seção Lançar Menos Exceções
fonte
Em teoria, um bloco try / catch não terá efeito no comportamento do código, a menos que uma exceção realmente ocorra. Existem algumas circunstâncias raras, no entanto, em que a existência de um bloco de tentativa / captura pode ter um efeito importante, e outras incomuns, mas dificilmente obscuras, nas quais o efeito pode ser perceptível. A razão para isso é que o código fornecido, como:
o compilador pode otimizar a instrução1 com base no fato de que a instrução2 é executada antes da instrução3. Se o compilador puder reconhecer que o item1 não tem efeitos colaterais e o item2 não usa x, ele pode omitir completamente o item1. Se [como neste caso] a coisa1 for cara, isso pode ser uma grande otimização, embora os casos em que a coisa1 seja cara também sejam aqueles em que o compilador provavelmente menos otimizará. Suponha que o código tenha sido alterado:
Agora existe uma sequência de eventos em que a instrução3 poderia ser executada sem a instrução2 ter sido executada. Mesmo que nada no código for
thing2
capaz de gerar uma exceção, seria possível que outro thread pudesse usar umInterlocked.CompareExchange
para perceber queq
foi limpo e configurá-lo comoThread.ResetAbort
, e então executar umaThread.Abort()
instrução before2 que escreveu seu valor emx
. Então ocatch
seria executadoThread.ResetAbort()
[via delegadoq
], permitindo que a execução continuasse com a instrução3. É claro que essa sequência de eventos seria excepcionalmente improvável, mas é necessário um compilador para gerar código que funcione de acordo com as especificações, mesmo quando esses eventos improváveis ocorrem.Em geral, é muito mais provável que o compilador perceba oportunidades de deixar de fora pedaços simples de código que códigos complexos e, portanto, seria raro uma tentativa / captura afetar o desempenho muito se exceções nunca forem lançadas. Ainda assim, existem algumas situações em que a existência de um bloco try / catch pode impedir otimizações que - mas para o try / catch - teriam permitido que o código fosse executado mais rapidamente.
fonte
Embora " Prevenir é melhor que manipular ", na perspectiva de desempenho e eficiência, poderíamos escolher o try-catch sobre a pré-varicação. Considere o código abaixo:
Aqui está o resultado:
fonte
Consulte a discussão sobre a implementação try / catch para obter uma discussão sobre como os blocos try / catch funcionam e como algumas implementações têm sobrecarga alta e outras zero, quando nenhuma exceção ocorre. Em particular, acho que a implementação do Windows de 32 bits tem uma sobrecarga alta e a implementação de 64 bits não.
fonte