Em Java, usar throw / catch como parte da lógica quando não há realmente um erro geralmente é uma má ideia (em parte), porque lançar e capturar uma exceção é caro, e fazê-lo muitas vezes em um loop geralmente é muito mais lento do que outros estruturas de controle que não envolvem o lançamento de exceções.
Minha pergunta é: é o custo incorrido no lançamento / captura em si ou ao criar o objeto Exception (já que ele obtém muitas informações de tempo de execução, incluindo a pilha de execução)?
Em outras palavras, se eu fizer
Exception e = new Exception();
mas não jogue, isso representa a maior parte do custo de arremesso, ou o arremesso de arremesso + captura está custando caro?
Não estou perguntando se colocar código em um bloco try / catch aumenta o custo de executar esse código, estou perguntando se pegar a exceção é a parte cara ou criar (chamar o construtor) a exceção é a parte cara .
Outra maneira de perguntar isso é: se eu fizesse uma instância de Exception e a jogasse e pegasse repetidamente, isso seria significativamente mais rápido do que criar uma nova exceção toda vez que eu lançar?
fonte
Respostas:
Criar um objeto de exceção não é mais caro do que criar outros objetos regulares. O custo principal está oculto no
fillInStackTrace
método nativo , que percorre a pilha de chamadas e coleta todas as informações necessárias para criar um rastreamento de pilha: classes, nomes de métodos, números de linhas etc.O mito sobre altos custos de exceção vem do fato que a maioria dos
Throwable
construtores chama implicitamentefillInStackTrace
. No entanto, há um construtor para criar umThrowable
sem rastreamento de pilha. Ele permite que você jogue lançamentos muito rápidos para instanciar. Outra maneira de criar exceções leves é substituirfillInStackTrace
.Agora, que tal lançar uma exceção?
De fato, depende de onde uma exceção lançada é capturada .
Se for capturado no mesmo método (ou, mais precisamente, no mesmo contexto, já que o contexto pode incluir vários métodos devido a inlining),
throw
será tão rápido e simples quantogoto
(é claro, após a compilação do JIT).No entanto, se um
catch
bloco estiver em algum lugar mais profundo da pilha, a JVM precisará desenrolar os quadros da pilha, e isso poderá demorar significativamente mais. Demora ainda mais, se houversynchronized
blocos ou métodos envolvidos, porque desenrolar implica na liberação de monitores pertencentes a quadros de pilha removidos.Eu poderia confirmar as afirmações acima com benchmarks adequados, mas felizmente não preciso fazer isso, pois todos os aspectos já estão perfeitamente cobertos no post do engenheiro de desempenho do HotSpot Alexey Shipilev: O desempenho excepcional da exceção de Lil ' .
fonte
A primeira operação na maioria dos
Throwable
construtores é preencher o rastreamento de pilha, que é onde está a maior parte da despesa.Há, no entanto, um construtor protegido com um sinalizador para desativar o rastreamento de pilha. Esse construtor também é acessível ao estender
Exception
. Se você criar um tipo de exceção personalizado, poderá evitar a criação do rastreamento de pilha e obter um melhor desempenho à custa de menos informações.Se você criar uma única exceção de qualquer tipo por meios normais, poderá repeti-la várias vezes sem a sobrecarga de preenchimento no rastreamento de pilha. No entanto, seu rastreamento de pilha refletirá onde foi construído, não onde foi lançado em uma instância específica.
As versões atuais do Java fazem algumas tentativas para otimizar a criação do rastreamento de pilha. O código nativo é chamado para preencher o rastreamento de pilha, que registra o rastreamento em uma estrutura nativa mais leve. Correspondentes Java
StackTraceElement
objetos são preguiçosamente criado a partir desse registro apenas quando osgetStackTrace()
,printStackTrace()
ou outros métodos que exigem o traço são chamados.Se você eliminar a geração de rastreamento de pilha, o outro custo principal é desenrolar a pilha entre o arremesso e a captura. Quanto menos quadros intermediários forem encontrados antes da exceção ser capturada, mais rápido será.
Projete seu programa para que exceções sejam lançadas apenas em casos realmente excepcionais, e é difícil justificar otimizações como essas.
fonte
Há uma boa anotação sobre exceções aqui.
http://shipilev.net/blog/2014/exceptional-performance/
A conclusão é que a construção do traço da pilha e o desenrolamento da pilha são as peças caras. O código abaixo aproveita um recurso no
1.7
qual podemos ativar e desativar os rastreamentos de pilha. Podemos então usar isso para ver que tipo de custo os diferentes cenários têmA seguir, são mostrados os horários apenas para a criação de objetos. Eu adicionei
String
aqui para que você possa ver que, sem a pilha sendo escrita, quase não há diferença na criação de umJavaException
Objeto e umString
. Com a escrita de pilha ativada, a diferença é dramática, ou seja, pelo menos uma ordem de magnitude mais lenta.A seguir, mostra quanto tempo levou para retornar de um arremesso a uma profundidade específica um milhão de vezes.
O seguinte é quase certamente uma simplificação bruta ...
Se tivermos uma profundidade de 16 com a gravação de pilha ativada, a criação de objetos levará aproximadamente ~ 40% do tempo, o rastreamento de pilha real será responsável pela grande maioria disso. ~ 93% da instanciação do objeto JavaException se deve ao rastreamento da pilha que está sendo realizado. Isso significa que o desenrolar da pilha nesse caso está demorando os outros 50% do tempo.
Quando desligamos a criação de objetos de rastreamento de pilha, é responsável por uma fração muito menor, ou seja, 20%, e o desenrolamento de pilha agora responde por 80% do tempo.
Nos dois casos, o desenrolamento da pilha leva uma grande parte do tempo total.
Os quadros de pilha neste exemplo são pequenos em comparação com o que você normalmente encontraria.
Você pode espiar o bytecode usando javap
isto é, para o método 4 ...
fonte
A criação do rastreio
Exception
com umanull
pilha leva tanto tempo quanto o blocothrow
etry-catch
juntos. No entanto, o preenchimento do rastreamento da pilha leva em média 5x mais tempo .Criei a seguinte referência para demonstrar o impacto no desempenho. Eu adicionei o
-Djava.compiler=NONE
à configuração de execução para desativar a otimização do compilador. Para medir o impacto da criação do rastreamento de pilha, estendi aException
classe para aproveitar o construtor sem pilha:O código de referência é o seguinte:
Resultado:
Isso significa que criar um
NoStackException
é aproximadamente tão caro quanto jogar repetidamente o mesmoException
. Também mostra que a criaçãoException
e o preenchimento de um rastreamento de pilha levam aproximadamente quatro vezes mais.fonte
Esta parte da pergunta ...
Parece perguntar se a criação de uma exceção e o armazenamento em cache em algum lugar melhora o desempenho. Sim. É o mesmo que desativar a pilha que está sendo gravada na criação do objeto, porque já foi feita.
Estes são os horários que recebi, leia a advertência depois disso ...
É claro que o problema com isso é o rastreamento da pilha agora aponta para onde você instancia o objeto e não para onde foi lançado.
fonte
Usando a resposta do @ AustinD como ponto de partida, fiz alguns ajustes. Código na parte inferior.
Além de adicionar o caso em que uma instância de exceção é lançada repetidamente, também desativei a otimização do compilador para que possamos obter resultados precisos de desempenho. Eu adicionei
-Djava.compiler=NONE
aos argumentos da VM, conforme esta resposta . (No eclipse, edite o Run Configuration → Arguments para configurar este argumento da VM)Os resultados:
Portanto, criar a exceção custa cerca de cinco vezes mais do que jogar + pegá-la. Supondo que o compilador não otimize muito o custo.
Para comparação, aqui está o mesmo teste sem desativar a otimização:
Código:
fonte