Continuo vendo as pessoas dizerem que as exceções são lentas, mas nunca vejo nenhuma prova. Portanto, em vez de perguntar se são, vou perguntar como as exceções funcionam nos bastidores, para que eu possa tomar decisões sobre quando usá-las e se são lentas.
Pelo que eu sei, exceções são o mesmo que fazer um retorno várias vezes, exceto que ele também verifica após cada retorno se precisa fazer outro ou parar. Como ele verifica quando deve parar de retornar? Eu acho que há uma segunda pilha que contém o tipo de exceção e uma localização da pilha, ela retorna até chegar lá. Eu também estou supondo que a única vez que esta segunda pilha é tocada é em um lançamento e em cada tentativa / pega. A implementação de AFAICT de um comportamento semelhante com códigos de retorno levaria a mesma quantidade de tempo. Mas tudo isso é apenas um palpite, então quero saber o que realmente acontece.
Como as exceções realmente funcionam?
Respostas:
Em vez de adivinhar, decidi realmente olhar para o código gerado com um pequeno pedaço de código C ++ e uma instalação um pouco antiga do Linux.
Compilei-o com
g++ -m32 -W -Wall -O3 -save-temps -c
e examinei o arquivo de montagem gerado._ZN11MyExceptionD1Ev
éMyException::~MyException()
, então o compilador decidiu que precisava de uma cópia não embutida do destruidor.Surpresa! Não há instruções extras no caminho de código normal. Em vez disso, o compilador gerou blocos de código de correção fora de linha extras, referenciados por meio de uma tabela no final da função (que na verdade é colocada em uma seção separada do executável). Todo o trabalho é feito nos bastidores pela biblioteca padrão, com base nessas tabelas (
_ZTI11MyException
istypeinfo for MyException
).OK, isso não foi realmente uma surpresa para mim, eu já sabia como esse compilador fazia isso. Continuando com a saída da montagem:
Aqui, vemos o código para lançar uma exceção. Embora não haja sobrecarga extra simplesmente porque uma exceção pode ser lançada, obviamente há muita sobrecarga em realmente lançar e capturar uma exceção. A maior parte está escondida dentro
__cxa_throw
, que deve:Compare isso com o custo de simplesmente retornar um valor e você verá por que as exceções devem ser usadas apenas para retornos excepcionais.
Para finalizar, o resto do arquivo de montagem:
Os dados typeinfo.
Ainda mais tabelas de tratamento de exceções e informações extras variadas.
Portanto, a conclusão, pelo menos para GCC no Linux: o custo é espaço extra (para os manipuladores e tabelas) se as exceções são lançadas ou não, mais o custo extra de analisar as tabelas e executar os manipuladores quando uma exceção é lançada. Se você usar exceções em vez de códigos de erro, e um erro é raro, ele pode ser mais rápido , pois você não tem mais a sobrecarga de testar os erros.
Caso queira mais informações, em particular o que todas as
__cxa_
funções fazem, consulte a especificação original de onde vieram:fonte
A lentidão das exceções era verdade nos velhos tempos.
Na maioria dos compiladores modernos, isso não é mais verdade.
Nota: Só porque temos exceções, não significa que não usamos códigos de erro também. Quando o erro puder ser tratado localmente, use os códigos de erro. Quando os erros exigem mais contexto para correção, use exceções: Eu escrevi muito mais eloquentemente aqui: Quais são os princípios que orientam sua política de tratamento de exceções?
O custo do código de tratamento de exceções quando nenhuma exceção está sendo usada é praticamente zero.
Quando uma exceção é lançada, algum trabalho é realizado.
Mas você tem que comparar isso com o custo de retornar códigos de erro e verificá-los até o ponto em que o erro pode ser tratado. Ambos consomem mais tempo para escrever e manter.
Também há uma pegadinha para os novatos:
embora os objetos Exception devam ser pequenos, algumas pessoas colocam muitas coisas dentro deles. Então você tem o custo de copiar o objeto de exceção. A solução é dupla:
Na minha opinião, eu apostaria que o mesmo código com exceções é mais eficiente ou pelo menos tão comparável quanto o código sem as exceções (mas tem todo o código extra para verificar os resultados de erro da função). Lembre-se de que você não está obtendo nada de graça - o compilador está gerando o código que você deveria ter escrito em primeiro lugar para verificar os códigos de erro (e geralmente o compilador é muito mais eficiente do que um humano).
fonte
Existem várias maneiras de implementar exceções, mas normalmente elas contarão com algum suporte subjacente do sistema operacional. No Windows, este é o mecanismo estruturado de tratamento de exceções.
Há uma discussão decente sobre os detalhes no Projeto de código: Como um compilador C ++ implementa o tratamento de exceções
A sobrecarga de exceções ocorre porque o compilador precisa gerar código para manter o controle de quais objetos devem ser destruídos em cada frame de pilha (ou mais precisamente no escopo) se uma exceção se propagar fora desse escopo. Se uma função não tiver variáveis locais na pilha que exijam que destruidores sejam chamados, ela não deve ter uma penalidade de desempenho em relação ao tratamento de exceções.
Usar um código de retorno só pode desfazer um único nível da pilha por vez, enquanto um mecanismo de tratamento de exceção pode pular muito mais para baixo na pilha em uma operação se não houver nada para fazer nos quadros de pilha intermediários.
fonte
Matt Pietrek escreveu um excelente artigo sobre Win32 Structured Exception Handling . Embora este artigo tenha sido escrito originalmente em 1997, ele ainda se aplica hoje (mas é claro que se aplica apenas ao Windows).
fonte
Este artigo examina o problema e basicamente descobre que, na prática, há um custo de tempo de execução para exceções, embora o custo seja bastante baixo se a exceção não for lançada. Bom artigo, recomendado.
fonte
Um amigo meu escreveu um pouco sobre como o Visual C ++ lida com exceções alguns anos atrás.
http://www.xyzw.de/c160.html
fonte
Todas boas respostas.
Além disso, pense em como é muito mais fácil depurar código que faz 'se verifica' como portas no topo dos métodos, em vez de permitir que o código lance exceções.
Meu lema é que é fácil escrever um código que funcione. O mais importante é escrever o código para a próxima pessoa que o olhar. Em alguns casos, é você em 9 meses, e você não quer xingar seu nome!
fonte