Eu estava assistindo Systematic Error Handling in C ++ - Andrei Alexandrescu afirma que as exceções em C ++ são muito lentas.
Isso ainda é verdade para C ++ 98?
Eu estava assistindo Systematic Error Handling in C ++ - Andrei Alexandrescu afirma que as exceções em C ++ são muito lentas.
Isso ainda é verdade para C ++ 98?
Respostas:
O principal modelo usado hoje para exceções (Itanium ABI, VC ++ 64 bits) são as exceções do modelo Zero-Cost.
A ideia é que em vez de perder tempo configurando um guarda e verificando explicitamente a presença de exceções em todos os lugares, o compilador gere uma tabela lateral que mapeia qualquer ponto que possa lançar uma exceção (Contador de programa) para uma lista de manipuladores. Quando uma exceção é lançada, esta lista é consultada para escolher o manipulador correto (se houver) e a pilha é desfeita.
Em comparação com a
if (error)
estratégia típica :if
quando ocorre uma exceçãoO custo, no entanto, não é trivial de medir:
dynamic_cast
teste para cada manipulador)Portanto, a maioria das falhas de cache e, portanto, não é trivial em comparação com o código puro da CPU.
Nota: para mais detalhes, leia o relatório TR18015, capítulo 5.4 Tratamento de exceções (pdf)
Portanto, sim, as exceções são lentas no caminho excepcional , mas são mais rápidas do que as verificações explícitas (
if
estratégia) em geral.Nota: Andrei Alexandrescu parece questionar isso "mais rápido". Eu pessoalmente vi as coisas mudarem para os dois lados, alguns programas sendo mais rápidos com exceções e outros sendo mais rápidos com ramificações, então, de fato, parece haver uma perda de otimizabilidade em certas condições.
Isso importa ?
Eu diria que não. Um programa deve ser escrito tendo em mente a legibilidade , não o desempenho (pelo menos, não como primeiro critério). Exceções devem ser usadas quando se espera que o chamador não possa ou não deseje lidar com a falha na hora e passá-la para cima na pilha. Bônus: em C ++ 11, as exceções podem ser distribuídas entre threads usando a Biblioteca Padrão.
Isso é sutil, porém, eu afirmo que
map::find
não deveria jogar, mas estou bem emmap::find
retornar umchecked_ptr
que joga se uma tentativa de cancelar a referência falhar porque é nulo: no último caso, como no caso da classe que Alexandrescu introduziu, o chamador escolhe entre a verificação explícita e a dependência de exceções. Capacitar o chamador sem dar a ele mais responsabilidade geralmente é um sinal de bom design.fonte
abort
permitirá que você meça a pegada de tamanho binário e verifique se o tempo de carregamento / i-cache se comporta de maneira semelhante. Claro, melhor não acertar em nenhum dosabort
...Quando a pergunta foi postada eu estava a caminho do médico, com um táxi esperando, então só tive tempo para um breve comentário. Mas tendo agora comentado e votado a favor e contra, é melhor adicionar minha própria resposta. Mesmo que a resposta de Matthieu já seja muito boa.
As exceções são especialmente lentas em C ++, em comparação com outras linguagens?
Re a reivindicação
Se isso é literalmente o que Andrei afirma, então pela primeira vez ele é muito enganador, se não totalmente errado. Para exceções levantadas / lançadas é sempre lento em comparação com outras operações básicas na linguagem, independentemente da linguagem de programação . Não apenas em C ++ ou mais em C ++ do que em outras linguagens, como indica a alegação.
Em geral, principalmente independentemente do idioma, os dois recursos básicos da linguagem que são ordens de magnitude mais lentos do que o resto, porque se traduzem em chamadas de rotinas que lidam com estruturas de dados complexas, são
lançamento de exceção, e
alocação de memória dinâmica.
Felizmente, em C ++, muitas vezes é possível evitar ambos no código de tempo crítico.
Infelizmente, não existe almoço grátis , mesmo que a eficiência padrão do C ++ chegue bem perto. :-) Pois a eficiência ganha ao evitar o lançamento de exceções e a alocação dinâmica de memória geralmente é obtida pela codificação em um nível inferior de abstração, usando C ++ apenas como um “C melhor”. E menor abstração significa maior “complexidade”.
Maior complexidade significa mais tempo gasto em manutenção e pouco ou nenhum benefício com a reutilização de código, que são custos monetários reais, mesmo se difíceis de estimar ou medir. Ou seja, com C ++ pode-se, se desejar, trocar alguma eficiência do programador por eficiência de execução. Fazer isso é em grande parte uma decisão de engenharia e intuição, porque na prática apenas o ganho, e não o custo, pode ser facilmente estimado e medido.
Há alguma medida objetiva de desempenho de lançamento de exceção C ++?
Sim, o comitê internacional de padronização C ++ publicou um relatório técnico sobre o desempenho do C ++, TR18015 .
O que significa que as exceções são “lentas”?
Significa principalmente que um
throw
pode demorar muito tempo ™ em comparação a, por exemplo, umaint
atribuição, devido à procura do manipulador.Como TR18015 discute em sua seção 5.4 "Exceções", existem duas estratégias principais de implementação de tratamento de exceção,
a abordagem em que cada
try
-bloco configura dinamicamente a captura de exceções, de modo que uma pesquisa na cadeia dinâmica de manipuladores seja realizada quando uma exceção é lançada, ea abordagem em que o compilador gera tabelas de consulta estáticas que são usadas para determinar o manipulador de uma exceção lançada.
A primeira abordagem muito flexível e geral é quase forçada no Windows de 32 bits, enquanto na terra de 64 bits e em * nix-land a segunda abordagem muito mais eficiente é comumente usada.
Além disso, conforme o relatório discute, para cada abordagem, existem três áreas principais onde o tratamento de exceções impacta a eficiência:
try
-blocos,funções regulares (oportunidades de otimização), e
throw
-expressões.Principalmente, com a abordagem do manipulador dinâmico (Windows de 32 bits), o tratamento de exceções tem um impacto nos
try
blocos, principalmente independentemente da linguagem (porque isso é forçado pelo esquema de Tratamento de Exceções Estruturado do Windows ), enquanto a abordagem de tabela estática tem custo praticamente zero paratry
- blocos. Discutir isso exigiria muito mais espaço e pesquisa do que é prático para uma resposta SO. Portanto, consulte o relatório para obter detalhes.Infelizmente o relatório, de 2006, já está um pouco datado do final de 2012 e, pelo que eu sei, não há nada comparável que seja mais recente.
Outra perspectiva importante é que o impacto do uso de exceções no desempenho é muito diferente da eficiência isolada dos recursos de linguagem de suporte, porque, como observa o relatório,
Por exemplo:
Custos de manutenção devido a diferentes estilos de programação (correção)
if
Verificação de falha de site de chamada redundante versus centralizadatry
Problemas de cache (por exemplo, um código mais curto pode caber no cache)
O relatório tem uma lista diferente de aspectos a serem considerados, mas de qualquer forma a única maneira prática de obter fatos concretos sobre a eficiência de execução é provavelmente implementar o mesmo programa usando exceção e não usando exceções, dentro de um limite definido no tempo de desenvolvimento e com desenvolvedores familiarizado com cada forma e, em seguida, MEASURE .
Qual é uma boa maneira de evitar a sobrecarga de exceções?
A correção quase sempre supera a eficiência.
Sem exceções, o seguinte pode acontecer facilmente:
Algum código P destina-se a obter um recurso ou computar algumas informações.
O código de chamada C deveria ter verificado o sucesso / falha, mas não o faz.
Um recurso inexistente ou informação inválida é usado no código após C, causando confusão geral.
O principal problema é o ponto (2), onde com o esquema usual de código de retorno , o código de chamada C não é forçado a verificar.
Existem duas abordagens principais que forçam essa verificação:
Onde P lança uma exceção diretamente quando falha.
Onde P retorna um objeto que C deve inspecionar antes de usar seu valor principal (caso contrário, uma exceção ou terminação).
A segunda abordagem foi, AFAIK, descrita pela primeira vez por Barton e Nackman em seu livro * Scientific and Engineering C ++: Uma introdução com técnicas avançadas e exemplos , onde eles introduziram uma classe chamada
Fallow
para um resultado de função “possível”. Uma classe semelhante chamadaoptional
agora é oferecida pela biblioteca Boost. E você mesmo pode implementar facilmente umaOptional
classe, usando umstd::vector
portador de valor para o caso de resultado não POD.Com a primeira abordagem, o código de chamada C não tem escolha a não ser usar técnicas de tratamento de exceções. Com a segunda abordagem, no entanto, o código de chamada C pode decidir se fará a
if
verificação baseada ou o tratamento geral de exceção. Assim, a segunda abordagem permite fazer o compromisso entre o programador e a eficiência do tempo de execução.Qual é o impacto dos vários padrões C ++ no desempenho de exceção?
C ++ 98 foi o primeiro padrão C ++. Para exceções, ele introduziu uma hierarquia padrão de classes de exceção (infelizmente, um tanto imperfeita). O principal impacto no desempenho foi a possibilidade de especificações de exceção (removidas no C ++ 11), que, entretanto, nunca foram totalmente implementadas pelo compilador principal do Windows C ++ Visual C ++: Visual C ++ aceita a sintaxe de especificação de exceção do C ++ 98, mas apenas ignora especificações de exceção.
C ++ 03 foi apenas uma retificação técnica de C ++ 98. A única realmente nova no C ++ 03 era a inicialização de valor . O que não tem nada a ver com exceções.
Com o C ++ 11, as especificações gerais de exceção padrão foram removidas e substituídas pela
noexcept
palavra - chave.O padrão C ++ 11 também adicionou suporte para armazenar e relançar exceções, o que é ótimo para propagar exceções C ++ em retornos de chamada da linguagem C. Esse suporte restringe efetivamente como a exceção atual pode ser armazenada. No entanto, até onde eu sei, isso não afeta o desempenho, exceto na medida em que no código mais recente, o tratamento de exceções pode ser usado mais facilmente em ambos os lados de um retorno de chamada da linguagem C.
fonte
longjmp
acessa o manipulador.try..finally
construção pode ser implementada sem desenrolar da pilha. F #, C # e Java são implementadostry..finally
sem usar o desenrolamento de pilha. Você apenaslongjmp
para o handler (como já expliquei).Você nunca pode reclamar sobre o desempenho, a menos que converta o código para o assembly ou faça um benchmark.
Aqui está o que você vê: (banco rápido)
O código de erro não é sensível à porcentagem de ocorrência. As exceções têm um pouco de sobrecarga, desde que nunca sejam lançadas. Depois de jogá-los, a miséria começa. Neste exemplo, ele é lançado para 0%, 1%, 10%, 50% e 90% dos casos. Quando as exceções são lançadas 90% do tempo, o código é 8 vezes mais lento do que o caso em que as exceções são lançadas 10% do tempo. Como você vê, as exceções são muito lentas. Não os use se forem lançados com frequência. Se seu aplicativo não tiver requisitos de tempo real, sinta-se à vontade para lançá-los se ocorrerem muito raramente.
Você vê muitas opiniões contraditórias sobre eles. Mas, finalmente, as exceções são lentas? Eu não julgo. Basta observar o benchmark.
fonte
Depende do compilador.
O GCC, por exemplo, era conhecido por ter um desempenho muito ruim ao lidar com exceções, mas isso melhorou consideravelmente nos últimos anos.
Mas observe que o tratamento de exceções deve - como o nome diz - ser a exceção e não a regra no design de seu software. Quando você tem um aplicativo que lança tantas exceções por segundo que afeta o desempenho e isso ainda é considerado uma operação normal, você deve pensar em fazer as coisas de forma diferente.
As exceções são uma ótima maneira de tornar o código mais legível, tirando do caminho todo aquele código desajeitado de tratamento de erros, mas assim que se tornam parte do fluxo normal do programa, elas se tornam realmente difíceis de seguir. Lembre-se de que a
throw
é basicamente umgoto catch
disfarce.fonte
throw new Exception
é um Java-ismo. como regra, nunca se deve lançar ponteiros.Sim, mas isso não importa. Por quê?
Leia:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
Basicamente, isso diz que usar exceções como Alexandrescu descreveu (desaceleração 50x porque eles usam
catch
comoelse
) é simplesmente errado. Dito isso, para pessoas que gostam de fazer assim, eu desejo que C ++ 22 :) adicionaria algo como:(note que isso teria que ser a linguagem principal, pois é basicamente um compilador gerando código a partir de um existente)
PS também note que mesmo que as exceções sejam tão lentas ... não é um problema se você não gastar muito tempo nessa parte do código durante a execução ... Por exemplo, se a divisão flutuante for lenta e você a aumentar 4x mais rápido, não importa se você gasta 0,3% do seu tempo fazendo a divisão de FP ...
fonte
Como in silico disse que sua implementação depende, mas em geral as exceções são consideradas lentas para qualquer implementação e não devem ser usadas em código de alto desempenho.
EDIT: Não estou dizendo para não usá-los, mas para código intensivo de desempenho, é melhor evitá-los.
fonte