67 votes

Que peut faire C++ RTTI indésirables?

En regardant la VERSION de la documentation, ils mentionnent qu' il est nécessaire d'utiliser un formulaire personnalisé de RTTI", et c'est la raison pour laquelle ils ont isa<>, cast<> et dyn_cast<> basées sur des modèles de fonctions.

Habituellement, la lecture qu'une bibliothèque reimplements certaines fonctionnalités de base d'une langue est une terrible odeur de code et invite simplement à exécuter. Cependant, ce n'est LLVM qu'on parle de: les gars travaillent sur un compilateur C++ et C++ runtime. Si ils ne savent pas ce qu'ils font, je suis assez bien vissé, car je préfère clang de la gcc version fournie avec Mac OS.

Encore, moins expérimenté qu'eux, je suis de gauche, vous demandez-vous quels sont les écueils de la normale RTTI. Je sais qu'il ne fonctionne que pour les types qui ont un v-table, mais qui ne soulève deux questions:

  • Depuis que vous avez juste besoin d'une méthode virtuelle pour avoir une vtable, pourquoi ne pas simplement une marque de la méthode d' virtual? Virtuel destructeurs semblent être de bons à cela.
  • Si leur solution n'est pas l'utilisation régulière de RTTI, une idée de comment il a été mis en œuvre?

81voto

Chris Lattner Points 556

Il y a plusieurs raisons pourquoi LLVM roule ses propres RTTI système. Ce système est simple et puissant, et décrits dans une section de la LLVM Manuel du Programmeur. Comme une autre affiche a souligné, les Normes de Codage soulève deux problèmes majeurs avec C++ RTTI: 1) le coût de l'espace et 2) la mauvaise performance de l'utiliser.

Le coût de l'espace de RTTI est assez élevé: chaque classe avec une vtable (au moins une méthode virtuelle) obtient RTTI de l'information, qui comprend le nom de la classe et de l'information au sujet de ses classes de base. Cette information est utilisée pour mettre en œuvre le typeid de l'opérateur ainsi que dynamic_cast. Parce que ce coût est payé pour chaque classe avec une vtable (et non, PGO et lien-temps optimisations ne vous aide pas, parce que la vtable de points pour le RTTI info) LLVM construit avec -fno-rtti. Empiriquement, cela permet d'économiser de l'ordre de 5 à 10% de la taille de l'exécutable, ce qui est assez considérable. LLVM n'a pas besoin d'un équivalent de typeid, donc les garder autour de noms (parmi d'autres choses dans type_info) pour chaque classe est juste un gaspillage d'espace.

La mauvaise performance est assez facile de voir si vous faites un peu de benchmarking ou de regarder le code généré pour les opérations simples. La LLVM isa<> opérateur généralement compile le bas à une seule charge et une comparaison avec une constante (bien que les classes de contrôle basée sur la façon dont ils mettent en œuvre leurs classof méthode). Voici un exemple trivial:

#include "llvm/Constants.h"
using namespace llvm;
bool isConstantInt(Value *V) { return isa<ConstantInt>(V); }

Cette compile:

$ clang t.cc -S -o - -O3-I$HOME/llvm/include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANTS_MACROS -mkernel -fomit-frame-pointer
...
__Z13isConstantIntPN4llvm5ValueE:
 cmpb $9, 8(%rdi)
 sete %al
 movzbl %al, %eax
ret

(si vous n'avez pas lu l'assemblée) est une charge et de la comparer à une constante. En revanche, l'équivalent avec dynamic_cast est:

#include "llvm/Constants.h"
using namespace llvm;
bool isConstantInt(Value *V) { return dynamic_cast<ConstantInt*>(V) != 0; }

qui compile:

clang t.cc -S -o - -O3-I$HOME/llvm/include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANTS_MACROS -mkernel -fomit-frame-pointer
...
__Z13isConstantIntPN4llvm5ValueE:
 pushq %rax
 xorb %al, %al
 testq %de l'aqr, %rdi
 je LBB0_2
 xorl %esi, %esi
 movq $-1, %rcx
 xorl %edx, %edx
 callq ___dynamiques_cast
 testq %rax, %rax
 setne %al
LBB0_2:
 movzbl %al, %eax
 popq %rdx
ret

C'est beaucoup plus de code, mais le tueur est l'appel à __dynamiques_exprimés, ce qui a alors à ramper à travers le RTTI structures de données et de faire une très général, calculée dynamiquement à pied à travers ce genre de choses. C'est de plusieurs ordres de grandeur inférieure à celle d'une charge et de les comparer.

Ok, ok, donc c'est plus lent, pourquoi est-ce important? C'est important parce que LLVM fait BEAUCOUP de type de contrôles. De nombreuses parties de la optimiseurs sont construits autour de la correspondance de motif spécifique des constructions dans le code et d'effectuer des substitutions sur eux. Pour exemple, voici un peu de code pour la mise en correspondance d'un modèle simple (qui sait déjà que Op0/Op1 sont la gauche et la droite d'un entier de soustraire l'opération):

  // (X*2) - X -> X
  if (match(Op0, m_Mul(m_Specific(Op1), m_ConstantInt<2>())))
    return Op1;

L'opérateur de match et m_* sont modèle metaprograms qui se résument à une série de isa/dyn_cast appels, dont chacune doit faire une vérification de type. À l'aide de dynamic_cast pour ce genre de fines pattern matching serait brutalement et showstoppingly lent.

Enfin, il y a un autre point, qui est l'un de l'expressivité. Les différents "rtti" opérateurs que LLVM utilise sont utilisés pour exprimer des choses différentes: vérification de type, dynamic_cast, forcé (affirmant) en fonte, la gestion des valeurs null etc. C++de dynamic_cast n'est pas (natif) offrent cette fonctionnalité.

En fin de compte, il y a deux façons de voir cette situation. Sur le côté négatif, C++ RTTI est à la fois trop étroitement définie pour ce que beaucoup de gens veulent (réflexion totale) et est trop lente pour être utile, même pour des choses simples comme ce que LLVM. Sur le côté positif, le langage C++ est assez puissant pour que nous puissions définir des abstractions comme ceci comme code de bibliothèque, et de refuser de l'aide de la fonctionnalité du langage. Une de mes choses préférées au sujet de C++ est comment puissant et élégant, les bibliothèques peuvent être. RTTI n'est pas encore très élevée chez mes fonctionnalités préférées de C++ :) !

-Chris

15voto

Jerry Coffin Points 237758

Le LLVM normes de codage semblent répondre à cette question assez bien:

Dans un effort pour réduire le code et la taille de l'exécutable, LLVM n'utilise pas de RTTI (par exemple dynamic_cast<>) ou à des exceptions. Ces deux fonctions du langage de violer le général C++ principe de "vous ne payez que pour ce que vous utilisez", à l'origine de l'exécutable de ballonnements, même si les exceptions ne sont jamais utilisés dans la base de code, ou si RTTI n'est jamais utilisé pour une classe. De ce fait, nous les désactiver globalement dans le code.

Cela dit, LLVM ne faire usage fréquent d'un roulé à la main sous forme de RTTI que l'utilisation de modèles tels que isa<> cast<>, et dyn_cast<>. Cette forme de RTTI est optionnel et peut être ajouté à n'importe quelle classe. Il est également sensiblement plus efficace que le dynamic_cast<>.

10voto

Mikael Persson Points 7174

Ici est un grand article sur la RTTI et pourquoi vous pourriez avoir besoin pour restaurer votre propre version de celui-ci.

Je ne suis pas un expert sur le C++ RTTI, mais j'ai mis en œuvre mes propres RTTI parce qu'il y a certainement des raisons pourquoi vous avez besoin de le faire. Tout d'abord, le C++ RTTI système n'est pas très riche en fonctionnalités, bref, tout ce que vous pouvez faire est de conversion de type et d'obtenir des informations de base. Que faire si, au moment de l'exécution, vous avez une chaîne de caractères contenant le nom d'une classe, et que vous voulez construire un objet de cette classe, bonne chance de le faire avec C++ RTTI. Aussi, C++ RTTI est pas vraiment (ou facilement) portable sur l'ensemble des modules (vous ne pouvez pas identifier la classe d'un objet qui a été créé à partir d'un autre module (/dll ou exe). De même, le C++ RTTI de la mise en œuvre est spécifique pour le compilateur, et il est généralement coûteux en termes de frais généraux additionnels pour mettre en œuvre ce pour tous les types. Enfin, il n'est pas vraiment persistant, de sorte qu'il ne peut pas vraiment être utilisé pour le fichier de sauvegarde/chargement par exemple (par exemple, vous souhaiterez peut-être enregistrer les données d'un objet dans un fichier, mais vous voulez également pour sauver le "typeid" de sa classe, de telle sorte que, au moment du chargement, vous savez qui est l'objet à créer afin de charger ces données, qui ne peuvent être réalisés de manière fiable avec C++ RTTI). Pour toutes ou certaines de ces raisons, de nombreux cadres ont leur propre RTTI (de très facile à très riche en fonctionnalités). Des exemples sont wxWidget, LLVM, coup de pouce.La sérialisation, etc.. ce n'est vraiment pas rare.

Depuis que vous avez juste besoin d'une méthode virtuelle pour avoir une vtable, pourquoi ne pas simplement la marque d'une méthode virtuelle? Virtuel destructeurs semblent être de bons à cela.

C'est sans doute que leur RTTI système utilise trop. Les fonctions virtuelles sont à la base de liaison dynamique (au moment de l'exécution de liaison), et par conséquent, il est fondamentalement nécessaire pour effectuer tout type de run-time type identification ou d'information (pas seulement requis par le C++ RTTI, mais toute la mise en œuvre de RTTI devra compter sur les appels virtuels d'une manière ou d'une autre).

Si leur solution n'est pas l'utilisation régulière de RTTI, une idée de comment il a été mis en œuvre?

Bien sûr, vous pouvez rechercher RTTI implémentations en C++. J'ai fait de mon propre et il y a beaucoup de bibliothèques qui ont leur propre RTTI. Il est assez simple à écrire, vraiment. Fondamentalement, tout ce que vous avez besoin est un moyen de représenter uniquement un type (c'est à dire le nom de la classe, ou certains mutilé de la version de celui-ci, ou même un ID unique pour chaque classe), une sorte de structure analogue à l' type_info qui contient toutes les informations sur le type que vous avez besoin, alors vous avez besoin d'un "caché" fonction virtuelle dans chaque classe sur ce type de retour d'information sur demande (si cette fonction est écrasé dans chaque classe dérivée, il fonctionnera). Il y a, bien sûr, certaines des choses qui peut être fait, comme un singleton référentiel de tous les types, peut-être avec les associés de l'usine de fonctions (cela peut être utile pour créer des objets d'un type quand tout ce qui est connu au moment de l'exécution est le nom du type, comme une chaîne de caractères ou le type de l'ID). Aussi, vous voudrez peut-être ajouter quelques fonctions virtuelles pour permettre la dynamique de la conversion de type (généralement, cela se fait par l'appel de la plupart des dérivés de la classe de fonction de distribution et d'exécution static_cast jusqu'à le type que vous souhaitez en fonte).

4voto

Matthieu M. Points 101624

La principale raison est qu'ils luttent pour maintenir l'utilisation de la mémoire aussi bas que possible.

RTTI est uniquement disponible pour les classes qui disposent d'au moins une méthode virtuelle, ce qui signifie que les instances de la classe contiendra un pointeur vers la table virtuelle.

Sur une version 64 bits de l'architecture (ce qui est courant aujourd'hui), un seul pointeur est de 8 octets. Car le compilateur à instancier un tas de petits objets, ce qui ajoute jusqu'à assez rapidement.

Il y a donc un effort continu pour supprimer des fonctions virtuelles, autant que possible (et pratique), et de mettre en œuvre ce qui aurait été de fonctions virtuelles avec l' switch d'instruction, qui a même la vitesse d'exécution, mais significativement plus faible impact de mémoire.

Leur souci constant pour la mémoire de la consommation a payé, en ce que Clang consomme beaucoup moins de mémoire que la gcc, par exemple, ce qui est important lorsque vous offrez à la bibliothèque de clients.

D'autre part, cela signifie aussi que l'ajout d'un nouveau type de nœud généralement des résultats dans l'édition de code dans un bon nombre de fichiers parce que chaque commutateur doivent être adaptés (heureusement, les compilateurs d'émettre un avertissement si vous manquez un membre enum dans un switch). Donc, ils ont accepté de faire de l'entretien un peu plus difficile dans le nom de l'efficacité de mémoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X