Question

Existe-t-il une pénalité de performances d'exécution lors de l'utilisation d'interfaces (classes de base abstraites) en C ++?

Était-ce utile?

La solution

Réponse courte: Non.

Réponse longue: Ce n'est pas la classe de base ou le nombre d'ancêtres d'une classe dans sa hiérarchie qui affecte sa vitesse. Le seul coût est le coût d'un appel de méthode.

Un appel de méthode non virtuel a un coût (mais peut être en ligne)

Un appel de méthode virtuelle a un coût légèrement plus élevé, car vous devez rechercher la méthode à appeler avant de l’appeler (mais il s’agit d’un tableau simple, recherchez et non une recherche). Puisque toutes les méthodes d’une interface sont virtuelles par définition, ce coût existe.

À moins d'écrire une application sensible à la vitesse extrême, cela ne devrait pas poser de problème. La clarté supplémentaire que vous obtiendrez de l’utilisation d’une interface compense généralement toute diminution de vitesse perçue.

Autres conseils

Les fonctions appelées à l'aide de la répartition virtuelle ne sont pas en ligne

Il existe un type de pénalité pour les fonctions virtuelles qui est facile à oublier: les appels virtuels ne sont pas en ligne dans une situation (commune) où le type de l’objet n’est pas connu au moment de la compilation. Si votre fonction est petite et adaptée à l’alignement en ligne, cette pénalité peut être très importante, car vous ajoutez non seulement un temps système d’appel, mais le compilateur est également limité quant à la façon dont il peut optimiser la fonction d’appel (il doit supposer que la fonction virtuelle Si certains registres ou emplacements de mémoire ont été modifiés, il ne peut pas propager de valeurs constantes entre l'appelant et l'appelé).

Le coût des appels virtuels dépend de la plate-forme

En ce qui concerne la pénalité de frais d’appel par rapport à un appel de fonction normal, la réponse dépend de votre plate-forme cible. Si vous ciblez un PC avec un processeur x86 / x64, l'appel d'une fonction virtuelle est très peu pénalisant, car le processeur x86 / x64 moderne peut effectuer une prédiction de branche sur les appels indirects. Toutefois, si vous ciblez un PowerPC ou une autre plate-forme RISC, la pénalité d’appel virtuel peut être assez importante, car les appels indirects ne sont jamais prédits sur certaines plates-formes (Cf. Meilleures pratiques de développement pour plates-formes croisées PC / Xbox 360 ).

Il existe une petite pénalité par appel de fonction virtuelle par rapport à un appel normal. Il est peu probable que vous constatiez une différence à moins que vous ne passiez des centaines de milliers d’appels à la seconde, et le prix vaut souvent la peine d’être payé pour une clarté de code supplémentaire.

Lorsque vous appelez une fonction virtuelle (par exemple via une interface), le programme doit faire une recherche de la fonction dans un tableau pour voir quelle fonction appeler pour cet objet. Cela donne une petite pénalité par rapport à un appel direct à la fonction.

En outre, lorsque vous utilisez une fonction virtuelle, le compilateur ne peut pas insérer l'appel de fonction en ligne. Par conséquent, l'utilisation d'une fonction virtuelle pour certaines petites fonctions peut être pénalisée. C’est généralement la plus grosse performance " hit " vous êtes susceptible de voir. Ce n'est vraiment un problème que si la fonction est petite et appelée plusieurs fois, par exemple dans une boucle.

Une autre alternative applicable dans certains cas est la compilation polymorphisme avec des modèles. C'est utile, par exemple, quand vous voulez faire un choix de mise en œuvre au début du programme, et utilisez-le ensuite pour la durée de l'exécution. Un exemple avec polymorphisme d'exécution

class AbstractAlgo
{
    virtual int func();
};

class Algo1 : public AbstractAlgo
{
    virtual int func();
};

class Algo2 : public AbstractAlgo
{
    virtual int func();
};

void compute(AbstractAlgo* algo)
{
      // Use algo many times, paying virtual function cost each time

}   

int main()
{
    int which;
     AbstractAlgo* algo;

    // read which from config file
    if (which == 1)
       algo = new Algo1();
    else
       algo = new Algo2();
    compute(algo);
}

Idem en utilisant le polymorphisme de compilation

class Algo1
{
    int func();
};

class Algo2
{
    int func();
};


template<class ALGO>  void compute()
{
    ALGO algo;
      // Use algo many times.  No virtual function cost, and func() may be inlined.
}   

int main()
{
    int which;
    // read which from config file
    if (which == 1)
       compute<Algo1>();
    else
       compute<Algo2>();
}

Je ne pense pas que la comparaison des coûts se situe entre un appel de fonction virtuel et un appel de fonction direct. Si vous envisagez d'utiliser une classe de base abstraite (interface), vous pouvez effectuer l'une des actions suivantes en fonction du type dynamique d'un objet. Vous devez faire ce choix en quelque sorte. Une option consiste à utiliser des fonctions virtuelles. Une autre option consiste à activer le type d'objet, soit via RTTI (potentiellement coûteux), soit en ajoutant une méthode type () à la classe de base (ce qui augmente potentiellement l'utilisation de la mémoire de chaque objet). Le coût de l’appel de fonction virtuelle doit donc être comparé au coût de l’alternative et non au coût de la non-réalisation.

La plupart des gens notent la pénalité d'exécution, et à juste titre.

Cependant, dans mon expérience de travail sur de grands projets, les avantages d’interfaces claires et d’une bonne encapsulation compensent rapidement le gain de vitesse. Le code modulaire peut être échangé pour une implémentation améliorée, le résultat est donc un gain important.

Votre kilométrage peut varier et dépend clairement de l'application que vous développez.

Il convient de noter que le coût des appels de fonctions virtuelles peut varier d’une plate-forme à une autre. Sur les consoles, elles sont peut-être plus visibles, car appel d’ordinaire vtable signifie un cache cache et peut prédire les branches.

Notez que l'héritage multiple gonfle l'instance d'objet avec plusieurs pointeurs vtable. Avec G ++ sur x86, si votre classe a une méthode virtuelle et aucune classe de base, vous avez un pointeur sur vtable. Si vous avez une classe de base avec des méthodes virtuelles, vous avez toujours un pointeur sur vtable. Si vous avez deux classes de base avec des méthodes virtuelles, vous avez deux pointeurs de vtable sur chaque instance .

Ainsi, avec l'héritage multiple (c'est-à-dire l'implémentation d'interfaces en C ++), vous payez des classes de base fois la taille du pointeur dans la taille de l'instance de l'objet. L’augmentation de l’empreinte mémoire peut avoir des conséquences indirectes sur les performances.

L'utilisation de classes de base abstraites en C ++ impose généralement l'utilisation d'une table de fonctions virtuelle. Tous vos appels d'interface vont être examinés dans cette table. Le coût est minime comparé à un appel de fonction brut, assurez-vous donc que vous devez aller plus vite que cela avant de vous en préoccuper.

La seule différence principale que je connaisse est que, puisque vous n'utilisez pas de classe concrète, l'inligne est (beaucoup?) plus difficile à faire.

La seule chose à laquelle je peux penser est que les méthodes virtuelles sont un peu plus lentes à appeler que les méthodes non virtuelles, car l'appel doit passer par le table de méthodes virtuelles .

Cependant, c’est une mauvaise raison de bousiller votre conception. Si vous avez besoin de plus de performances, utilisez un serveur plus rapide.

Comme pour toute classe contenant une fonction virtuelle, une vtable est utilisée. Évidemment, invoquer une méthode via un mécanisme de répartition tel qu'une table virtuelle est plus lent qu'un appel direct, mais dans la plupart des cas, vous pouvez vivre avec cela.

Oui, mais rien de remarquable à ma connaissance. La performance est affectée par le "indirection" que vous avez dans chaque appel de méthode.

Cependant, cela dépend vraiment du compilateur que vous utilisez, car certains compilateurs ne sont pas en mesure d’inscrire en ligne les appels de méthodes dans les classes héritées de la classe de base abstraite.

Si vous voulez être sûr de faire vos propres tests.

Oui, il y a une pénalité. Pour améliorer les performances de votre plate-forme, vous pouvez utiliser une classe non abstraite sans fonctions virtuelles. Ensuite, utilisez un pointeur de fonction membre sur votre fonction non virtuelle.

Je sais que ce point de vue est inhabituel, mais le seul fait de mentionner ce problème me fait penser que vous avez trop réfléchi dans la structure de classe. J'ai vu de nombreux systèmes qui comportaient beaucoup trop de "niveaux d'abstraction" et qui, à eux seuls, les exposaient à de graves problèmes de performances, non à cause du coût des appels de méthode, mais à cause de la tendance à passer des appels inutiles. Si cela se produit sur plusieurs niveaux, c'est un tueur. jeter un coup d'œil

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top