Je comprends que l'utilisation de RTTI pose un problème de ressources, mais quelle est sa taille? Partout où j'ai regardé, il suffit de dire que "RTTI est cher", mais aucun d'entre eux ne donne réellement de référence ou de données quantitatives concernant la mémoire, le temps du processeur ou la vitesse.
Alors, quel est le prix du RTTI? Je pourrais l'utiliser sur un système embarqué où je n'ai que 4 Mo de RAM, donc chaque bit compte.
Edit: Selon la réponse de S. Lott , ce serait mieux si j'incluais ce que je fais réellement. J'utilise une classe pour transmettre des données de différentes longueurs et qui peuvent effectuer différentes actions , il serait donc difficile de le faire en utilisant uniquement des fonctions virtuelles. Il semble que l'utilisation de quelques dynamic_cast
s pourrait remédier à ce problème en permettant aux différentes classes dérivées de traverser les différents niveaux tout en leur permettant d'agir complètement différemment.
D'après ce que j'ai compris, dynamic_cast
utilise RTTI, donc je me demandais dans quelle mesure il serait faisable de l'utiliser sur un système limité.
la source
dynamic_cast
en C ++, et maintenant, 9 fois sur 10, lorsque je "casse" le programme avec le débogueur, il se brise dans la fonction interne de conversion dynamique. C'est sacrément lent.Réponses:
Quel que soit le compilateur, vous pouvez toujours économiser sur l'exécution si vous pouvez vous permettre de le faire
au lieu de
Le premier implique une seule comparaison de
std::type_info
; ce dernier implique nécessairement de parcourir un arbre d'héritage plus des comparaisons.Au-delà ... comme tout le monde le dit, l'utilisation des ressources est spécifique à l'implémentation.
Je suis d'accord avec les commentaires de tous les autres selon lesquels l'auteur de la proposition devrait éviter le RTTI pour des raisons de conception. Cependant, il y a de bonnes raisons d'utiliser RTTI (principalement à cause de boost :: any). Cela à l'esprit, il est utile de connaître son utilisation réelle des ressources dans les implémentations courantes.
J'ai récemment fait un tas de recherches sur RTTI dans GCC.
tl; dr: RTTI dans GCC utilise un espace négligeable et
typeid(a) == typeid(b)
est très rapide, sur de nombreuses plates-formes (Linux, BSD et peut-être plates-formes embarquées, mais pas mingw32). Si vous savez que vous serez toujours sur une plate-forme bénie, RTTI est très proche de la gratuité.Détails granuleux:
GCC préfère utiliser un ABI C ++ "indépendant du fournisseur" [1], et utilise toujours cet ABI pour les cibles Linux et BSD [2]. Pour les plates-formes qui prennent en charge cette ABI et également une liaison faible,
typeid()
renvoie un objet cohérent et unique pour chaque type, même au-delà des limites de liaison dynamique. Vous pouvez tester&typeid(a) == &typeid(b)
, ou simplement vous fier au fait que le test portabletypeid(a) == typeid(b)
compare simplement un pointeur en interne.Dans l'ABI préférée de GCC, une table vtable de classe contient toujours un pointeur vers une structure RTTI par type, bien qu'elle puisse ne pas être utilisée. Ainsi, un
typeid()
appel lui-même ne devrait coûter autant que toute autre recherche de vtable (le même que l'appel d'une fonction membre virtuelle), et le support RTTI ne devrait pas utiliser d'espace supplémentaire pour chaque objet.D'après ce que je peux comprendre, les structures RTTI utilisées par GCC (ce sont toutes les sous-classes de
std::type_info
) ne contiennent que quelques octets pour chaque type, à part le nom. Il n'est pas clair pour moi si les noms sont présents dans le code de sortie, même avec-fno-rtti
. Dans tous les cas, le changement de taille du binaire compilé doit refléter le changement d'utilisation de la mémoire d'exécution.Une expérience rapide (en utilisant GCC 4.4.3 sur Ubuntu 10.04 64 bits) montre que cela augmente en
-fno-rtti
fait la taille binaire d'un programme de test simple de quelques centaines d'octets. Cela se produit de manière cohérente dans les combinaisons de et . Je ne sais pas pourquoi la taille augmenterait; une possibilité est que le code STL de GCC se comporte différemment sans RTTI (puisque les exceptions ne fonctionneront pas).-g
-O3
[1] Connu sous le nom d'Itanium C ++ ABI, documenté à http://www.codesourcery.com/public/cxx-abi/abi.html . Les noms sont horriblement déroutants: le nom fait référence à l'architecture de développement d'origine, bien que la spécification ABI fonctionne sur de nombreuses architectures, y compris i686 / x86_64. Les commentaires dans la source interne de GCC et dans le code STL font référence à Itanium comme étant la "nouvelle" ABI contrairement à l '"ancienne" qu'ils utilisaient auparavant. Pire encore, le «nouveau» / Itanium ABI fait référence à toutes les versions disponibles via
-fabi-version
; «l'ancienne» ABI était antérieure à cette gestion des versions. GCC a adopté l'ABI Itanium / versioned / "new" dans la version 3.0; l '«ancien» ABI a été utilisé en 2.95 et avant, si je lis correctement leurs changelogs.[2] Je n'ai trouvé aucune ressource répertoriant
std::type_info
la stabilité des objets par plateforme. Pour les compilateurs J'ai eu accès, je suit:echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES
. Cette macro contrôle le comportement deoperator==
forstd::type_info
dans la STL de GCC, à partir de GCC 3.0. J'ai trouvé que mingw32-gcc obéit à l'ABI Windows C ++, où lesstd::type_info
objets ne sont pas uniques pour un type parmi les DLL;typeid(a) == typeid(b)
appelsstrcmp
sous les couvertures. Je suppose que sur les cibles intégrées à programme unique comme AVR, où il n'y a pas de code à lier, lesstd::type_info
objets sont toujours stables.la source
int
et il n'y a pas de vtable là-dedans :))the latter necessarily involves traversing an inheritance tree plus comparisons
. @CoryB Vous pouvez "vous permettre" de le faire lorsque vous n'avez pas besoin de prendre en charge la conversion à partir de l'ensemble de l'arborescence d'héritage. Par exemple, si vous voulez trouver tous les éléments de type X dans une collection, mais pas ceux qui dérivent de X, alors ce que vous devez utiliser est le premier. Si vous devez également rechercher toutes les instances dérivées, vous devrez utiliser cette dernière.Peut-être que ces chiffres aideraient.
Je faisais un test rapide en utilisant ceci:
5 cas ont été testés:
5 est juste mon code réel, car j'avais besoin de créer un objet de ce type avant de vérifier s'il est similaire à celui que j'ai déjà.
Sans optimisation
Pour lesquels les résultats étaient (j'ai fait la moyenne de quelques essais):
La conclusion serait donc:
typeid()
c'est plus de deux fois plus rapide quedyncamic_cast
.Avec optimisation (-Os)
La conclusion serait donc:
typeid()
est presque x20 plus rapide quedyncamic_cast
.Graphique
Le code
Comme demandé dans les commentaires, le code est ci-dessous (un peu brouillon, mais fonctionne). «FastDelegate.h» est disponible à partir d' ici .
la source
class a {}; class b : public a {}; class c : public b {};
lorsque la cible est une instance dec
fonctionnera correctement lors du test de la classeb
avecdynamic_cast
, mais pas avec latypeid
solution. Encore raisonnable cependant, +1Cela dépend de l'échelle des choses. Pour la plupart, il ne s'agit que de quelques vérifications et de quelques déréférences de pointeur. Dans la plupart des implémentations, en haut de chaque objet qui a des fonctions virtuelles, il y a un pointeur vers une vtable qui contient une liste de pointeurs vers toutes les implémentations de la fonction virtuelle sur cette classe. Je suppose que la plupart des implémentations utiliseraient ceci pour stocker un autre pointeur vers la structure type_info pour la classe.
Par exemple en pseudo-c ++:
En général, le véritable argument contre RTTI est l'incapacité de devoir modifier le code partout à chaque fois que vous ajoutez une nouvelle classe dérivée. Au lieu d'instructions de commutation partout, intégrez-les dans des fonctions virtuelles. Cela déplace tout le code qui est différent entre les classes dans les classes elles-mêmes, de sorte qu'une nouvelle dérivation a juste besoin de remplacer toutes les fonctions virtuelles pour devenir une classe pleinement fonctionnelle. Si vous avez déjà eu à parcourir une grande base de code pour chaque fois que quelqu'un vérifie le type d'une classe et fait quelque chose de différent, vous apprendrez rapidement à rester à l'écart de ce style de programmation.
Si votre compilateur vous permet de désactiver totalement RTTI, les économies de taille de code qui en résultent peuvent être significatives, avec un si petit espace RAM. Le compilateur doit générer une structure type_info pour chaque classe avec une fonction virtuelle. Si vous désactivez RTTI, toutes ces structures n'ont pas besoin d'être incluses dans l'image exécutable.
la source
Eh bien, le profileur ne ment jamais.
Comme j'ai une hiérarchie assez stable de 18 à 20 types qui ne change pas beaucoup, je me suis demandé si le simple fait d'utiliser un simple membre énuméré ferait l'affaire et éviterait le coût prétendument «élevé» du RTTI. J'étais sceptique si RTTI était en fait plus cher que la simple
if
déclaration qu'il introduit. Boy oh boy, c'est ça.Il s'avère que RTTI est cher, beaucoup plus cher qu'une
if
instruction équivalente ou qu'une simpleswitch
sur une variable primitive en C ++. La réponse de S.Lott n'est donc pas tout à fait correcte, il y a un coût supplémentaire pour RTTI, et ce n'est pas simplement dû à uneif
déclaration dans le mix. C'est parce que le RTTI est très cher.Ce test a été effectué sur le compilateur Apple LLVM 5.0, avec les optimisations de stock activées (paramètres de mode de publication par défaut).
Donc, j'ai ci-dessous 2 fonctions, dont chacune détermine le type concret d'un objet via 1) RTTI ou 2) un simple commutateur. Il le fait 50 000 000 fois. Sans plus tarder, je vous présente les durées d'exécution relatives pour 50 000 000 d'exécutions.
C'est vrai, cela a
dynamicCasts
pris 94% du temps d'exécution. Alors que leregularSwitch
bloc n'a pris que 3,3% .En bref: si vous pouvez vous permettre de vous connecter à un
enum
type comme je l'ai fait ci-dessous, je le recommanderais probablement, si vous avez besoin de faire du RTTI et que les performances sont primordiales. Il ne faut définir le membre qu'une seule fois (assurez-vous de l'obtenir via tous les constructeurs ), et assurez-vous de ne jamais l'écrire par la suite.Cela dit, cela ne devrait pas gâcher vos pratiques OOP ... il est uniquement destiné à être utilisé lorsque les informations de type ne sont tout simplement pas disponibles et que vous vous retrouvez coincé dans l'utilisation du RTTI.
la source
La manière standard:
Le RTTI standard est coûteux car il repose sur une comparaison de chaînes sous-jacentes et donc la vitesse du RTTI peut varier en fonction de la longueur du nom de classe.
La raison pour laquelle les comparaisons de chaînes sont utilisées est de le faire fonctionner de manière cohérente à travers les limites de la bibliothèque / DLL. Si vous construisez votre application de manière statique et / ou que vous utilisez certains compilateurs, vous pouvez probablement utiliser:
Ce qui n'est pas garanti de fonctionner (ne donnera jamais de faux positif, mais peut donner de faux négatifs) mais peut être jusqu'à 15 fois plus rapide. Cela repose sur l'implémentation de typeid () pour fonctionner d'une certaine manière et tout ce que vous faites est de comparer un pointeur de caractère interne. C'est aussi parfois équivalent à:
Vous pouvez cependant utiliser un hybride en toute sécurité qui sera très rapide si les types correspondent, et ce sera le pire des cas pour les types inégalés:
Pour comprendre si vous avez besoin d'optimiser cela, vous devez voir combien de temps vous passez à obtenir un nouveau paquet, par rapport au temps nécessaire pour traiter le paquet. Dans la plupart des cas, une comparaison de chaînes ne sera probablement pas une surcharge. (en fonction de votre classe ou de votre espace de noms :: longueur du nom de la classe)
Le moyen le plus sûr d'optimiser cela est d'implémenter votre propre typeid en tant qu'int (ou enum Type: int) dans le cadre de votre classe de base et de l'utiliser pour déterminer le type de la classe, puis d'utiliser simplement static_cast <> ou reinterpret_cast < >
Pour moi, la différence est d'environ 15 fois sur MS VS 2005 C ++ SP1 non optimisé.
la source
typeid::operator
travail . GCC sur une plate-forme prise en charge, par exemple, utilise déjà des comparaisons dechar *
s, sans forcer - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/… . Bien sûr, votre façon de faire permet à MSVC de se comporter beaucoup mieux que celui par défaut sur votre plate-forme, alors bravo, et je ne sais pas quelles sont les «cibles» qui utilisent des pointeurs de manière native ... mais mon point est que le comportement de MSVC ne l'est en aucun cas "La norme".Pour une simple vérification, RTTI peut être aussi bon marché qu'une comparaison avec un pointeur. Pour la vérification de l'héritage, cela peut être aussi coûteux que
strcmp
pour chaque type dans une arborescence d'héritage si vous allezdynamic_cast
du haut vers le bas dans une implémentation.Vous pouvez également réduire la surcharge en n'utilisant pas
dynamic_cast
et en vérifiant à la place le type explicitement via & typeid (...) == & typeid (type). Bien que cela ne fonctionne pas nécessairement pour les fichiers .dll ou tout autre code chargé dynamiquement, cela peut être assez rapide pour les éléments liés statiquement.Bien qu'à ce stade, c'est comme utiliser une instruction switch, alors voilà.
la source
Il est toujours préférable de mesurer les choses. Dans le code suivant, sous g ++, l'utilisation de l'identification de type codée à la main semble être environ trois fois plus rapide que RTTI. Je suis sûr qu'une implémentation codée à la main plus réaliste utilisant des chaînes au lieu de caractères serait plus lente, rapprochant les timings.
la source
Il y a quelque temps, j'ai mesuré les coûts de temps pour RTTI dans les cas spécifiques de MSVC et GCC pour un PowerPC 3 GHz. Dans les tests que j'ai exécutés (une application C ++ assez volumineuse avec un arbre de classes profond), chacun
dynamic_cast<>
coûte entre 0,8 μs et 2 μs, selon qu'il a réussi ou raté.la source
Cela dépend entièrement du compilateur que vous utilisez. Je comprends que certains utilisent des comparaisons de chaînes et d'autres utilisent de vrais algorithmes.
Votre seul espoir est d'écrire un exemple de programme et de voir ce que fait votre compilateur (ou au moins de déterminer combien de temps il faut pour exécuter un million
dynamic_casts
ou un million detypeid
s).la source
RTTI peut être bon marché et n'a pas nécessairement besoin d'un strcmp. Le compilateur limite le test pour exécuter la hiérarchie réelle, dans l'ordre inverse. Donc, si vous avez une classe C qui est un enfant de la classe B qui est un enfant de la classe A, dynamic_cast d'un A * ptr à un C * ptr n'implique qu'une seule comparaison de pointeurs et non deux (BTW, seul le pointeur de table vptr est par rapport). Le test est comme "if (vptr_of_obj == vptr_of_C) return (C *) obj"
Un autre exemple, si nous essayons de dynamic_cast de A * à B *. Dans ce cas, le compilateur vérifiera les deux cas (obj étant un C et obj étant un B) à tour de rôle. Cela peut également être simplifié en un seul test (la plupart du temps), car la table des fonctions virtuelles est une agrégation, donc le test reprend à "if (offset_of (vptr_of_obj, B) == vptr_of_B)" avec
offset_of = return sizeof (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B: 0
La disposition de la mémoire de
Comment le compilateur sait-il pour optimiser cela au moment de la compilation?
Au moment de la compilation, le compilateur connaît la hiérarchie actuelle des objets, il refuse donc de compiler une hiérarchie de types différente dynamic_casting. Ensuite, il lui suffit de gérer la profondeur de la hiérarchie et d'ajouter le nombre de tests inversé pour correspondre à cette profondeur.
Par exemple, cela ne compile pas:
la source
RTTI peut être "coûteux" car vous avez ajouté une instruction if à chaque fois que vous effectuez la comparaison RTTI. Dans les itérations profondément imbriquées, cela peut être coûteux. Dans quelque chose qui n'est jamais exécuté en boucle, c'est essentiellement gratuit.
Le choix est d'utiliser une conception polymorphe appropriée, en éliminant l'instruction if. Dans les boucles profondément imbriquées, c'est essentiel pour les performances. Sinon, cela n'a pas beaucoup d'importance.
Le RTTI est également coûteux car il peut masquer la hiérarchie des sous-classes (s'il y en a une). Cela peut avoir pour effet secondaire de supprimer "orienté objet" de la "programmation orientée objet".
la source
if
instruction que vous introduisez lorsque vous vérifiez les informations de type d'exécution de cette façon.