147 votes

Combien coûte RTTI?

Je comprends qu'il y est une ressource frappé de l'utilisation de RTTI, mais c'est quoi les dimensions? Partout où j'ai regardé juste dit que "RTTI est cher", mais aucun de ceux qui ont donner les points de repère ou de données quantitatives concernant la mémoire, le temps du processeur ou la vitesse.

Donc, juste comment cher est RTTI? Je pourrais l'utiliser sur un système embarqué, où je n'ai que 4 MO de RAM, de sorte que chaque geste compte.

Edit: Comme par S. Lott réponse, il serait mieux si je comprend ce que je suis en train de faire. J'utilise une classe pour transmettre des données de longueurs différentes et qui peuvent effectuer différentes actions, de sorte qu'il serait difficile de le faire en utilisant uniquement des fonctions virtuelles. Il semble que l'utilisation de quelques dynamic_casts pourraient remédier à ce problème en permettant aux différentes classes dérivées pour être passé à travers les différents niveaux encore leur permettent d'agir d'une manière complètement différente.

De ma compréhension, dynamic_cast utilise RTTI, donc je me demandais comment réalisable, il serait d'utiliser un système limitée.

114voto

sbrudenell Points 471

Quel que soit le compilateur, vous pouvez toujours enregistrer sur l'exécution si vous pouvez vous permettre de le faire

if (typeid(a) == typeid(b)) {
  B* ba = static_cast<B*>(&a);
  etc;
}

au lieu de

B* ba = dynamic_cast<B*>(&a);
if (ba) {
  etc;
}

Le premier implique une seule comparaison des std::type_info; le second implique nécessairement la traversée d'un arbre d'héritage plus des comparaisons.

Passé que ... comme tout le monde le dit, l'utilisation des ressources est mise en œuvre spécifique.

Je suis d'accord avec tous les autres commentaires que le demandeur devrait éviter RTTI pour des raisons de conception. Cependant, il sont de bonnes raisons d'utiliser RTTI (principalement en raison de boost::any). Dans cet esprit, il est utile de connaître ses réels de l'utilisation des ressources en commun des implémentations.

J'ai récemment fait un tas de recherches sur le RTTI dans GCC.

tl;dr: RTTI dans GCC utilise négligeable de l'espace et de l' typeid(a) == typeid(b) est très rapide, sur de nombreuses plates-formes (Linux, BSD et peut-être les plateformes embarquées, mais pas mingw32). Si vous savez que vous aurez toujours être un béni plate-forme, RTTI est très proche de gratuit.

Détails:

GCC préfère utiliser un particulier de "neutre" ABI C++ [1], et utilise toujours cette ABI pour Linux et BSD cibles[2]. Pour les plates-formes qui prennent en charge cette ABI et aussi la faible articulation, typeid() renvoie un uniforme et unique objet pour chaque type, même à travers la liaison dynamique des limites. Vous pouvez tester &typeid(a) == &typeid(b), ou simplement s'appuyer sur le fait que le portable de test typeid(a) == typeid(b) fait, il suffit de comparer un pointeur interne.

Dans GCC préféré de l'ABI, une classe vtable toujours est titulaire d'un pointeur pour chaque type de RTTI de la structure, même si elle peut ne pas être utilisée. Ainsi, un typeid() s'appeler lui-même doit seulement les coûts autant que tous les autres vtable de recherche (le même que l'appel d'une fonction membre virtuelle), et de RTTI de soutien ne devraient pas utiliser tout l'espace supplémentaire pour chaque objet.

À partir de ce que je peux faire, le RTTI structures utilisées par GCC (ce sont tous les sous-classes de std::type_info) seulement 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. De toute façon, le changement de la taille des binaires compilés devrait refléter le changement dans la mémoire d'exécution d'utilisation.

Une expérience rapide (à l'aide de GCC 4.4.3 sur Ubuntu 10.04 64 bits) montre que l' -fno-rtti fait augmente la taille du binaire d'un programme de test simple de quelques centaines d'octets. Cela se produit de manière uniforme à travers des combinaisons de -g et -O3. Je ne suis pas sûr pourquoi, la taille de l'augmentation; une possibilité est que GCC du code STL se comporte différemment sans RTTI (depuis exceptions ne fonctionnera pas).

[1] Connu comme le Itanium ABI C++, documentée à http://www.codesourcery.com/public/cxx-abi/abi.html. Les noms sont horriblement confus: le nom fait référence à l'origine du développement de l'architecture, bien que l'ABI spécification travaille sur beaucoup d'architectures i686/x86_64. Commentaires dans GCC interne de la source et code STL reportez-vous à Itanium comme le "nouveau" ABI contrairement à la "vieille" une ils ont utilisé avant. Pire, la "nouvelle"/Itanium ABI fait référence à toutes les versions disponibles par le biais -fabi-version; les "vieux" de LCA antérieurs à cette version. GCC a adopté le Itanium/versionnées/"nouveau" ABI dans la version 3.0, le "vieux" ABI a été utilisé dans de 2,95 et plus tôt, si je suis à la lecture de leurs changelogs correctement.

[2] je ne pouvais pas trouver toutes les listes de ressources std::type_info objet de la stabilité de la plate-forme. Pour les compilateurs j'ai eu accès à l', j'ai utilisé le suivant: echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES. Cette macro contrôle le comportement de l' operator== pour std::type_info dans du CCG STL, comme de GCC 3.0. Je n'ai trouver que mingw32-gcc obéit à la Windows ABI C++, où std::type_info objets ne sont pas uniques pour un type à travers les Dll, typeid(a) == typeid(b) des appels strcmp sous les couvertures. Je spécule que sur un seul programme de cibles embarquées comme AVR, où il n'y a pas de code pour le lien contre, std::type_info des objets sont toujours stables.

46voto

Izhaki Points 5883

Peut-être que ces chiffres seraient de l'aide.

J'ai été faire un test rapide en utilisant ceci:

  • GCC Horloge() + XCode du générateur de profils.
  • 100 000 000 d'itérations de boucle.
  • 2 x 2,66 GHz Dual-Core Intel Xeon.
  • La classe en question est dérivé à partir d'une seule classe de base.
  • typeid().nom() renvoie "N12fastdelegate13FastDelegate1Iivee"

5 Cas ont été testés:

1) dynamic_cast< FireType* >( mDelegate )
2) typeid( *iDelegate ) == typeid( *mDelegate )
3) typeid( *iDelegate ).name() == typeid( *mDelegate ).name()
4) &typeid( *iDelegate ) == &typeid( *mDelegate )
5) { 
       fastdelegate::FastDelegateBase *iDelegate;
       iDelegate = new fastdelegate::FastDelegate1< t1 >;
       typeid( *iDelegate ) == typeid( *mDelegate )
   }

5 est juste mon code, que j'avais besoin de créer un objet de ce type avant de vérifier si elle est similaire à l'un j'ai déjà.

Sans Optimisation

Pour quels ont été les résultats (j'ai en moyenne un peu court):

1)  1,840,000 Ticks (~2  Seconds) - dynamic_cast
2)    870,000 Ticks (~1  Second)  - typeid()
3)    890,000 Ticks (~1  Second)  - typeid().name()
4)    615,000 Ticks (~1  Second)  - &typeid()
5) 14,261,000 Ticks (~23 Seconds) - typeid() with extra variable allocations.

Donc, la conclusion serait:

  • Pour la distribution simple des cas, sans optimisation typeid() est plus de deux fois plus rapide que l' dyncamic_cast.
  • Sur une machine moderne de la différence entre les deux est d'environ 1 nanoseconde (un millionième de la milliseconde).

Avec Optimisation (-Os)

1)  1,356,000 Ticks - dynamic_cast
2)     76,000 Ticks - typeid()
3)     76,000 Ticks - typeid().name()
4)     75,000 Ticks - &typeid()
5)     75,000 Ticks - typeid() with extra variable allocations.

Donc, la conclusion serait:

  • Pour la distribution simple des cas, avec optimisation, typeid() est presque 20x plus vite que dyncamic_cast.

Graphique

enter image description here

Le Code

Comme demandé dans les commentaires, le code est ci-dessous (un peu brouillon, mais qui fonctionne). 'FastDelegate.h' est disponible à partir d' ici.

#include <iostream>
#include "FastDelegate.h"
#include "cycle.h"
#include "time.h"

// Undefine for typeid checks
#define CAST

class ZoomManager
{
public:
    template < class Observer, class t1 >
    void Subscribe( void *aObj, void (Observer::*func )( t1 a1 ) )
    {
        mDelegate = new fastdelegate::FastDelegate1< t1 >;

        std::cout << "Subscribe\n";
        Fire( true );
    }

    template< class t1 >
    void Fire( t1 a1 )
    {
        fastdelegate::FastDelegateBase *iDelegate;
        iDelegate = new fastdelegate::FastDelegate1< t1 >;

        int t = 0;
        ticks start = getticks();

        clock_t iStart, iEnd;

        iStart = clock();

        typedef fastdelegate::FastDelegate1< t1 > FireType;

        for ( int i = 0; i < 100000000; i++ ) {

#ifdef CAST
                if ( dynamic_cast< FireType* >( mDelegate ) )
#else
                // Change this line for comparisons .name() and & comparisons
                if ( typeid( *iDelegate ) == typeid( *mDelegate ) )
#endif
                {
                    t++;
                } else {
                    t--;
                }
        }

        iEnd = clock();
        printf("Clock ticks: %i,\n", iEnd - iStart );

        std::cout << typeid( *mDelegate ).name()<<"\n";

        ticks end = getticks();
        double e = elapsed(start, end);
        std::cout << "Elasped: " << e;
    }

    template< class t1, class t2 >
    void Fire( t1 a1, t2 a2 )
    {
        std::cout << "Fire\n";
    }

    fastdelegate::FastDelegateBase *mDelegate;
};

class Scaler
{
public:
    Scaler( ZoomManager *aZoomManager ) :
        mZoomManager( aZoomManager ) { }

    void Sub()
    {
        mZoomManager->Subscribe( this, &Scaler::OnSizeChanged );
    }

    void OnSizeChanged( int X  )
    {
        std::cout << "Yey!\n";        
    }
private:
    ZoomManager *mZoomManager;
};

int main(int argc, const char * argv[])
{
    ZoomManager *iZoomManager = new ZoomManager();

    Scaler iScaler( iZoomManager );
    iScaler.Sub();

    delete iZoomManager;

    return 0;
}

37voto

Eclipse Points 27662

Il dépend de l'échelle des choses. Pour la plupart, c'est juste un couple de contrôles et d'un peu de déréférence le pointeur. Dans la plupart des implémentations, en haut de chaque objet qui a des fonctions virtuelles, il est un pointeur vers une vtable qui contient une liste de pointeurs vers tous les implémentations de la fonction virtuelle sur cette classe. Je suppose que la plupart des implémentations serait l'utiliser pour stocker un autre pointeur de la type_info structure de la classe.

Par exemple en pseudo-c++:

struct Base
{
    virtual ~Base() {}
};

struct Derived
{
    virtual ~Derived() {}
};


int main()
{
    Base *d = new Derived();
    const char *name = typeid(*d).name(); // C++ way

    // faked up way (this won't actually work, but gives an idea of what might be happening in some implementations).
    const vtable *vt = reinterpret_cast<vtable *>(d);
    type_info *ti = vt->typeinfo;
    const char *name = ProcessRawName(ti->name);       
}

En général, le véritable argument contre RTTI est le unmaintainability d'avoir à modifier le code de partout, chaque fois que vous ajoutez une nouvelle classe dérivée. Au lieu de les instructions switch partout, facteur de ceux que les fonctions virtuelles. Cela bouge tout le code qui est différent entre les classes dans les classes elles-mêmes, de sorte qu'une nouvelle dérivation juste besoin de remplacer toutes les fonctions virtuelles pour devenir un bon fonctionnement de la classe. Si vous avez déjà eu à chasser à travers 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 allez vite apprendre à rester à l'écart de ce style de programmation.

Si votre compilateur permet de totalement désactiver RTTI, le final résultant de la taille du code économies peuvent être importantes si, avec un si petit espace RAM. Le compilateur a besoin de générer un type_info structure pour chaque classe, avec une fonction virtuelle. Si vous désactivez le RTTI, toutes ces structures ne doivent pas être inclus dans l'exécutable de l'image.

13voto

Marius Points 2008

La manière standard:

cout << (typeid(Base) == typeid(Derived)) << endl;

Standard RTTI est coûteuse, car elle repose sur un sous-jacent de comparaison de chaîne et donc de la vitesse de RTTI peut varier en fonction du nom de la classe de longueur.

La raison pour laquelle la comparaison des chaînes sont utilisés pour le faire fonctionner de façon uniforme dans bibliothèque/limites de la DLL. Si vous construisez votre application de manière statique et/ou vous utilisez certains compilateurs, alors vous pouvez probablement utiliser:

cout << (typeid(Base).name() == typeid(Derived).name()) << endl;

Ce qui n'est pas garanti (ne jamais donner un faux positif, mais peut donner de faux négatifs) mais peut-être jusqu'à 15 fois plus rapide. Cela repose sur la mise en œuvre de typeid() pour travailler dans un certain sens, et tout ce que vous faites est de comparer interne pointeur de char. C'est aussi parfois l'équivalent de:

cout << (&typeid(Base) == &typeid(Derived)) << endl;

Vous pouvez toutefois utiliser un hybride en toute sécurité qui va être très rapide si les types de match, et sera pire des cas pour les types incompatibles:

cout << ( typeid(Base).name() == typeid(Derived).name() || 
          typeid(Base) == typeid(Derived) ) << endl;

Pour comprendre si vous avez besoin pour optimiser cela, vous avez besoin de voir comment beaucoup de votre temps que vous passez à l'obtention d'un nouveau paquet, par rapport au temps qu'il faut pour traiter le paquet. Dans la plupart des cas, une comparaison de chaîne ne sera probablement pas un grand frais généraux. (en fonction de votre classe ou de l'espace::classe de longueur de nom)

La plus sûre façon d'optimiser, ce qui est à mettre en place votre propre typeid comme un int (ou d'un Type enum : int ) dans le cadre de votre classe de Base et l'utiliser pour déterminer le type de la classe, puis il suffit d'utiliser static_cast<> ou reinterpret_cast<>

Pour moi la différence est à peu près 15 fois sur unoptimized MS VS C++ 2005 SP1.

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