Entendo que há um acerto de recurso ao usar o RTTI, mas qual é o tamanho? Em todo lugar que olhei, apenas diz que "o RTTI é caro", mas nenhum deles realmente fornece benchmarks ou dados quantitativos sobre memória, tempo do processador ou velocidade.
Então, quão caro é o RTTI? Eu posso usá-lo em um sistema incorporado onde tenho apenas 4 MB de RAM, então cada bit conta.
Edit: Conforme a resposta de S. Lott , seria melhor se eu incluísse o que realmente estou fazendo. Estou usando uma classe para transmitir dados de diferentes comprimentos e que podem executar ações diferentes , por isso seria difícil fazer isso usando apenas funções virtuais. Parece que o uso de alguns dynamic_cast
s poderia solucionar esse problema, permitindo que as diferentes classes derivadas passassem pelos diferentes níveis, mas ainda assim permitisse que eles agissem de maneira completamente diferente.
Pelo meu entendimento, dynamic_cast
usa RTTI, então eu estava pensando em como seria viável usar em um sistema limitado.
fonte
dynamic_cast
em C ++ e agora, 9 em 10 vezes quando "quebro" o programa com o depurador, ele quebra dentro da função interna de conversão dinâmica. É muito lento.Respostas:
Independentemente do compilador, você sempre pode economizar em tempo de execução, se puder fazer isso
ao invés de
O primeiro envolve apenas uma comparação de
std::type_info
; o último envolve necessariamente atravessar uma árvore de herança mais comparações.Além disso, como todo mundo diz, o uso de recursos é específico da implementação.
Concordo com os comentários de todos os outros de que o remetente deve evitar a RTTI por motivos de design. No entanto, não são boas razões para utilizar RTTI (principalmente por causa do boost :: houver). Isso é útil para saber o uso real de recursos em implementações comuns.
Recentemente, fiz várias pesquisas sobre RTTI no GCC.
tl; dr: RTTI no GCC usa espaço desprezível e
typeid(a) == typeid(b)
é muito rápido, em muitas plataformas (Linux, BSD e talvez plataformas embarcadas, mas não no mingw32). Se você sabe que sempre estará em uma plataforma abençoada, o RTTI está muito perto de ser gratuito.Detalhes corajosos:
O GCC prefere usar um C ++ ABI "neutro em relação ao fornecedor" [1] e sempre usa esse ABI para destinos Linux e BSD [2]. Para plataformas que suportam essa ABI e também com ligação fraca,
typeid()
retorna um objeto consistente e exclusivo para cada tipo, mesmo através dos limites de vinculação dinâmica. Você pode testar&typeid(a) == &typeid(b)
ou apenas confiar no fato de que o teste portátiltypeid(a) == typeid(b)
, na verdade, apenas compara um ponteiro internamente.Na ABI preferida do GCC, uma tabela de classe sempre mantém um ponteiro para uma estrutura RTTI por tipo, embora possa não ser usada. Portanto, uma
typeid()
chamada em si só deve custar tanto quanto qualquer outra pesquisa de vtable (o mesmo que chamar uma função de membro virtual) e o suporte a RTTI não deve usar espaço extra para cada objeto.Pelo que pude entender, as estruturas RTTI usadas pelo GCC (todas essas são as subclasses de
std::type_info
) contêm apenas alguns bytes para cada tipo, além do nome. Não está claro para mim se os nomes estão presentes no código de saída, mesmo com-fno-rtti
. De qualquer forma, a alteração no tamanho do binário compilado deve refletir a alteração no uso da memória de tempo de execução.Uma experiência rápida (usando o GCC 4.4.3 no Ubuntu 10.04 de 64 bits) mostra que,
-fno-rtti
na verdade, aumenta o tamanho binário de um programa de teste simples em algumas centenas de bytes. Isso acontece consistentemente nas combinações de-g
e-O3
. Não sei por que o tamanho aumentaria; uma possibilidade é que o código STL do GCC se comporte de maneira diferente sem o RTTI (já que as exceções não funcionarão).[1] Conhecido como Itanium C ++ ABI, documentado em http://www.codesourcery.com/public/cxx-abi/abi.html . Os nomes são terrivelmente confusos: o nome se refere à arquitetura de desenvolvimento original, embora a especificação ABI funcione em várias arquiteturas, incluindo i686 / x86_64. Os comentários na fonte interna e no código STL do GCC referem-se ao Itanium como a "nova" ABI, em contraste com a "antiga" usada anteriormente. Pior, o "novo" / Itanium ABI refere-se a todas as versões disponíveis no
-fabi-version
; a ABI "antiga" antecedeu esse controle de versão. O GCC adotou a ITB Itanium / versioned / "new" na versão 3.0; a ABI "antiga" foi usada na versão 2.95 e anterior, se eu estiver lendo os registros de alterações corretamente.[2] Não consegui encontrar nenhuma
std::type_info
estabilidade de objeto da lista de recursos por plataforma. Para compiladores I teve acesso, eu usei o seguinte:echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES
. Essa macro controla o comportamento deoperator==
forstd::type_info
no STL do GCC, a partir do GCC 3.0. Eu descobri que o mingw32-gcc obedece à ABI do Windows C ++, onde osstd::type_info
objetos não são exclusivos para um tipo nas DLLs;typeid(a) == typeid(b)
chamadasstrcmp
debaixo das cobertas. Especulo que em destinos incorporados de programa único como o AVR, onde não há código para vincular, osstd::type_info
objetos são sempre estáveis.fonte
int
e não há vtable em que :))the latter necessarily involves traversing an inheritance tree plus comparisons
. @CoryB Você pode "se dar ao luxo" de fazê-lo quando não precisar dar suporte à conversão de toda a árvore de herança. Por exemplo, se você deseja encontrar todos os itens do tipo X em uma coleção, mas não aqueles que derivam de X, o que você deve usar é o primeiro. Se você também precisar encontrar todas as instâncias derivadas, precisará usá-las.Talvez esses números ajudem.
Eu estava fazendo um teste rápido usando isso:
5 casos foram testados:
5 é apenas o meu código real, pois eu precisava criar um objeto desse tipo antes de verificar se é semelhante a um que eu já tenho.
Sem otimização
Para os quais foram os resultados (calculei a média de algumas execuções):
Portanto, a conclusão seria:
typeid()
é mais do que duas vezes mais rápido quedyncamic_cast
.Com otimização (-Os)
Portanto, a conclusão seria:
typeid()
é quase 20 vezes mais rápido quedyncamic_cast
.Gráfico
O código
Conforme solicitado nos comentários, o código está abaixo (um pouco confuso, mas funciona). 'FastDelegate.h' está disponível aqui .
fonte
class a {}; class b : public a {}; class c : public b {};
quando o destino é uma instância dec
, funcionará bem ao testar a classeb
comdynamic_cast
, mas não com atypeid
solução. Ainda razoável, porém, +1Depende da escala das coisas. Na maioria das vezes, são apenas algumas verificações e algumas referências de ponteiro. Na maioria das implementações, na parte superior de cada objeto que possui funções virtuais, há um ponteiro para uma vtable que contém uma lista de ponteiros para todas as implementações da função virtual nessa classe. Eu acho que a maioria das implementações usaria isso para armazenar outro ponteiro para a estrutura type_info da classe.
Por exemplo, no pseudo-c ++:
Em geral, o argumento real contra o RTTI é a impossibilidade de manter o código em todos os lugares sempre que você adiciona uma nova classe derivada. Em vez de alternar instruções em todos os lugares, leve-as em funções virtuais. Isso move todo o código que é diferente entre as classes para as próprias classes, de modo que uma nova derivação só precisa substituir todas as funções virtuais para se tornar uma classe totalmente funcional. Se você já teve que procurar em uma grande base de códigos toda vez que alguém verifica o tipo de uma classe e faz algo diferente, rapidamente aprenderá a ficar longe desse estilo de programação.
Se o seu compilador permitir que você desative totalmente o RTTI, a economia final de tamanho de código resultante poderá ser significativa, com um espaço de RAM tão pequeno. O compilador precisa gerar uma estrutura type_info para cada classe com uma função virtual. Se você desativar o RTTI, todas essas estruturas não precisarão ser incluídas na imagem executável.
fonte
Bem, o criador de perfil nunca mente.
Como tenho uma hierarquia bastante estável de 18 a 20 tipos que não está mudando muito, perguntei-me se apenas o uso de um membro enum'd simples faria o truque e evitaria o suposto "alto" custo do RTTI. Fiquei cético se o RTTI fosse de fato mais caro do que apenas a
if
declaração que ele introduz. Garoto, oh garoto, é isso?Acontece que o RTTI é caro, muito mais caro que uma
if
instrução equivalente ou um simplesswitch
em uma variável primitiva em C ++. Então, a resposta de S. Lott não é completamente correto, não é custo extra para RTTI, e é não devido a apenas ter umaif
declaração na mistura. É porque o RTTI é muito caro.Esse teste foi realizado no compilador Apple LLVM 5.0, com as otimizações de estoque ativadas (configurações padrão do modo de liberação).
Então, eu tenho abaixo de 2 funções, cada uma das quais descobre o tipo concreto de um objeto via 1) RTTI ou 2) uma opção simples. Faz isso 50.000.000 de vezes. Sem mais delongas, apresento os tempos de execução relativos de 50.000.000 de execuções.
É isso mesmo, o que
dynamicCasts
levou 94% do tempo de execução. Enquanto oregularSwitch
bloco levou apenas 3,3% .Para encurtar a história: se você puder pagar a energia necessária para conectar um
enum
tipo como eu fiz abaixo, provavelmente o recomendaria, se você precisar fazer RTTI e o desempenho for primordial. É necessário definir o membro apenas uma vez (certifique-se de obtê-lo através de todos os construtores ) e nunca escrevê-lo posteriormente.Dito isto, isso não deve atrapalhar suas práticas de POO. Ele deve ser usado apenas quando as informações de tipo simplesmente não estão disponíveis e você se vê envolvido no uso de RTTI.
fonte
A maneira padrão:
O RTTI padrão é caro, pois depende de uma comparação de sequência subjacente e, portanto, a velocidade do RTTI pode variar dependendo do tamanho do nome da classe.
O motivo pelo qual as comparações de strings são usadas é fazê-lo funcionar de maneira consistente nos limites da biblioteca / DLL. Se você criar seu aplicativo estaticamente e / ou estiver usando determinados compiladores, provavelmente poderá usar:
O que não garante que funcione (nunca dará um falso positivo, mas pode dar falsos negativos), mas pode ser até 15 vezes mais rápido. Isso depende da implementação de typeid () para funcionar de uma certa maneira e tudo o que você está fazendo é comparar um ponteiro interno de char. Às vezes, isso também é equivalente a:
No entanto, você pode usar um híbrido com segurança, que será muito rápido se os tipos corresponderem e será o pior caso para tipos sem correspondência:
Para entender se você precisa otimizar isso, precisa ver quanto tempo gasta recebendo um novo pacote, em comparação com o tempo necessário para processar o pacote. Na maioria dos casos, uma comparação de cadeias provavelmente não será uma sobrecarga grande. (dependendo da sua classe ou namespace :: tamanho do nome da classe)
A maneira mais segura de otimizar isso é implementar seu próprio typeid como um int (ou um enum Type: int) como parte de sua classe Base e usá-lo para determinar o tipo da classe e, em seguida, basta usar static_cast <> ou reinterpret_cast < >
Para mim, a diferença é aproximadamente 15 vezes no MS VS 2005 C ++ SP1 não otimizado.
fonte
typeid::operator
funciona . O GCC em uma plataforma suportada, por exemplo, já usa comparações dechar *
s, sem a necessidade de forçá-lo - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/… . Claro, seu jeito faz com que o MSVC se comporte muito melhor do que o padrão em sua plataforma, então parabéns, e eu não sei quais são os "alguns destinos" que usam ponteiros nativamente ... mas meu ponto é que o comportamento do MSVC não é de forma alguma "Padrão".Para uma verificação simples, o RTTI pode ser tão barato quanto uma comparação de ponteiro. Para verificação de herança, pode ser tão caro quanto
strcmp
para todos os tipos em uma árvore de herança, se você estiverdynamic_cast
indo de cima para baixo em uma implementação disponível.Você também pode reduzir a sobrecarga não usando
dynamic_cast
e, em vez disso, verificando o tipo explicitamente via & typeid (...) == & typeid (type). Embora isso não funcione necessariamente para .dlls ou outro código carregado dinamicamente, pode ser bastante rápido para itens vinculados estaticamente.Embora, nesse ponto, seja como usar uma instrução switch, lá está você.
fonte
É sempre melhor medir as coisas. No código a seguir, em g ++, o uso da identificação de tipo codificado manualmente parece ser cerca de três vezes mais rápido que o RTTI. Tenho certeza de que uma implementação codificada à mão mais realista, usando strings em vez de caracteres, seria mais lenta, aproximando os tempos.
fonte
Há um tempo atrás, medi os custos de tempo para RTTI nos casos específicos de MSVC e GCC para um PowerPC de 3ghz. Nos testes que eu executei (um aplicativo C ++ bastante grande, com uma árvore de classes profunda), cada um
dynamic_cast<>
custa entre 0,8 μs e 2 μs, dependendo se ocorreu ou não.fonte
Isso depende inteiramente do compilador que você está usando. Entendo que alguns usam comparações de strings e outros usam algoritmos reais.
Sua única esperança é escrever um programa de exemplo e ver o que seu compilador faz (ou pelo menos determinar quanto tempo leva para executar um milhão
dynamic_casts
ou um milhão detypeid
s).fonte
O RTTI pode ser barato e não precisa necessariamente de um strcmp. O compilador limita o teste para executar a hierarquia real, em ordem inversa. Portanto, se você tiver uma classe C filha da classe B, filha da classe A, dynamic_cast de A * ptr para C * ptr implica apenas uma comparação de ponteiro e não duas (BTW, apenas o ponteiro da tabela vptr é comparado). O teste é como "se (vptr_of_obj == vptr_of_C) retorna (C *) obj"
Outro exemplo, se tentarmos transmitir dynamic_cast de A * para B *. Nesse caso, o compilador verificará os dois casos (obj sendo um C e obj sendo um B) alternadamente. Isso também pode ser simplificado para um único teste (na maioria das vezes), como a tabela de funções virtuais é feita como uma agregação, de modo que o teste é retomado para "if (offset_of (vptr_of_obj, B) == vptr_of_B)" com
offset_of = tamanho do retorno (tabela vptr)> = tamanho (vptr_of_B)? vptr_of_new_methods_in_B: 0
O layout da memória de
Como o compilador sabe otimizar isso em tempo de compilação?
No momento da compilação, o compilador conhece a hierarquia atual dos objetos e, portanto, se recusa a compilar hierarquia de tipos diferentes dynamic_casting. Depois, basta lidar com a profundidade da hierarquia e adicionar a quantidade invertida de testes para corresponder a essa profundidade.
Por exemplo, isso não compila:
fonte
O RTTI pode ser "caro" porque você adicionou uma instrução if toda vez que faz a comparação do RTTI. Em iterações profundamente aninhadas, isso pode ser caro. Em algo que nunca é executado em um loop, é essencialmente gratuito.
A escolha é usar o design polimórfico adequado, eliminando a instrução if. Em loops profundamente aninhados, isso é essencial para o desempenho. Caso contrário, não importa muito.
O RTTI também é caro porque pode obscurecer a hierarquia da subclasse (se houver mesmo). Pode ter o efeito colateral de remover o "orientado a objetos" da "programação orientada a objetos".
fonte
if
instrução que você introduz quando verifica as informações sobre o tipo de tempo de execução dessa maneira.