149 votes

Fonctions virtuelles et performances - C++

Dans la conception de mes classes, j'utilise abondamment les classes abstraites et les fonctions virtuelles. J'avais l'impression que les fonctions virtuelles affectaient les performances. Est-ce vrai ? Mais je pense que cette différence de performances n'est pas perceptible et que j'ai l'impression de faire une optimisation prématurée. N'est-ce pas ?

0 votes

Conformément à ma réponse, je suggère de fermer ce dossier en tant que doublon de stackoverflow.com/questions/113830

0 votes

2 votes

Si vous faites du calcul haute performance et du "number crunching", n'utilisez pas de virtualité au cœur du calcul : cela tue définitivement toutes les performances et empêche les optimisations au moment de la compilation. Pour l'initialisation ou la finalisation du programme, ce n'est pas important. Lorsque vous travaillez avec des interfaces, vous pouvez utiliser la virtualité comme vous le souhaitez.

198voto

Crashworks Points 22920

Votre question m'a rendu curieux, alors je me suis lancé et j'ai effectué quelques timings sur le processeur PowerPC de 3 GHz en ordre de marche avec lequel nous travaillons. Le test que j'ai effectué consistait à créer une simple classe de vecteur 4d avec des fonctions get/set.

class TestVec 
{
    float x,y,z,w; 
public:
    float GetX() { return x; }
    float SetX(float to) { return x=to; }  // and so on for the other three 
}

J'ai ensuite créé trois tableaux contenant chacun 1024 de ces vecteurs (assez petits pour tenir dans L1) et j'ai exécuté une boucle qui les ajoutait les uns aux autres (A.x = B.x + C.x) 1000 fois. J'ai exécuté cette boucle avec les fonctions définies comme inline , virtual et des appels de fonction réguliers. Voici les résultats :

  • en ligne : 8ms (0,65ns par appel)
  • direct : 68ms (5,53ns par appel)
  • virtuel : 160ms (13ns par appel)

Ainsi, dans ce cas (où tout tient dans le cache), les appels de fonctions virtuelles étaient environ 20 fois plus lents que les appels en ligne. Mais qu'est-ce que cela signifie réellement ? Chaque passage dans la boucle a causé exactement 3 * 4 * 1024 = 12,288 appels de fonction (1024 vecteurs fois quatre composantes fois trois appels par addition), ces temps représentent donc 1000 * 12,288 = 12,288,000 appels de fonction. La boucle virtuelle a pris 92 ms de plus que la boucle directe, de sorte que la surcharge supplémentaire par appel était de 7 %. nanosecondes par fonction.

J'en conclus que : Les fonctions virtuelles sont beaucoup plus lentes que les fonctions directes. no à moins que vous ne prévoyiez de les appeler dix millions de fois par seconde, cela n'a pas d'importance.

Voir aussi : comparaison de l'assemblage généré.

0 votes

Mais s'ils sont appelés plusieurs fois, ils peuvent souvent être moins chers que lorsqu'ils sont appelés une seule fois. Voir mon blog hors sujet : phresnel.org/blog les posts intitulés "Fonctions virtuelles considérées comme non nuisibles", mais cela dépend bien sûr de la complexité de vos codes.

24 votes

Mon test mesure un petit ensemble de fonctions virtuelles appelées de manière répétée. L'article de votre blog suppose que le coût en temps du code peut être mesuré en comptant les opérations, mais ce n'est pas toujours vrai ; le coût majeur d'une vfunc sur les processeurs modernes est la bulle du pipeline causée par une mauvaise prédiction de branche.

10 votes

Ceci serait un excellent point de repère pour gcc LTO (Link Time Optimization) ; essayez de le compiler à nouveau avec lto activé : gcc.gnu.org/wiki/LinkTimeOptimisation et voir ce qui se passe avec le facteur 20x

99voto

Greg Hewgill Points 356191

Une bonne règle de base est :

Ce n'est pas un problème de performance jusqu'à ce que vous puissiez le prouver.

L'utilisation de fonctions virtuelles aura un très léger effet sur les performances, mais il est peu probable qu'elle affecte les performances globales de votre application. Il est préférable de chercher à améliorer les performances au niveau des algorithmes et des E/S.

Un excellent article qui traite des fonctions virtuelles (et plus encore) est le suivant Les pointeurs de fonctions membres et les délégués C++ les plus rapides possibles .

1 votes

Qu'en est-il des fonctions virtuelles pures ? Affectent-elles les performances de quelque manière que ce soit ? Je m'interroge simplement car il semble qu'elles soient là simplement pour renforcer l'implémentation.

2 votes

@thomthom : Correct, il n'y a pas de différence de performance entre les fonctions virtuelles pures et les fonctions virtuelles ordinaires.

46voto

Chuck Points 138930

Quand l'Objective-C (où toutes les méthodes sont virtuelles) est le langage principal de l'iPhone et que l'on ne peut pas se passer de l'Objective-C Java est le langage principal d'Android, je pense qu'il est assez sûr d'utiliser les fonctions virtuelles C++ sur nos tours à double cœur de 3 GHz.

4 votes

Je ne suis pas sûr que l'iPhone soit un bon exemple de code performant : youtube.com/watch?v=Pdk2cJpSXLg

14 votes

@Crashworks : L'iPhone n'est pas du tout un exemple de code. C'est un exemple de matériel - spécifiquement matériel lent ce qui est le point que je faisais ici. Si ces langages réputés "lents" sont assez bons pour du matériel peu puissant, les fonctions virtuelles ne seront pas un gros problème.

58 votes

L'iPhone fonctionne avec un processeur ARM. Les processeurs ARM utilisés pour iOS sont conçus pour une utilisation à faible MHz et à faible consommation. Il n'y a pas de silicium pour la prédiction de branchement sur le processeur et donc pas de surcharge de performance due aux manques de prédiction de branchement des appels de fonctions virtuelles. De plus, la fréquence MHz du matériel iOS est suffisamment basse pour qu'un manque de cache ne bloque pas le processeur pendant 300 cycles d'horloge pendant qu'il récupère les données de la RAM. Les ratés du cache sont moins importants à des MHz inférieurs. En résumé, l'utilisation de fonctions virtuelles sur les appareils iOS n'entraîne pas de surcharge, mais il s'agit d'un problème matériel qui ne s'applique pas aux processeurs des ordinateurs de bureau.

41voto

Mark James Points 421

Dans les applications dont les performances sont très critiques (comme les jeux vidéo), un appel de fonction virtuelle peut être trop lent. Avec le matériel moderne, le plus gros problème de performance est le manque de cache. Si les données ne sont pas dans le cache, il peut s'écouler des centaines de cycles avant qu'elles ne soient disponibles.

Un appel de fonction normal peut générer une absence de cache d'instruction lorsque le CPU va chercher la première instruction de la nouvelle fonction et qu'elle n'est pas dans le cache.

Un appel à une fonction virtuelle doit d'abord charger le pointeur de la table virtuelle à partir de l'objet. Cela peut entraîner un manque de données dans le cache. Ensuite, il charge le pointeur de fonction à partir de la table virtuelle, ce qui peut entraîner une autre absence de données dans le cache. Enfin, il appelle la fonction, ce qui peut entraîner une absence d'instructions dans le cache, comme pour une fonction non virtuelle.

Dans de nombreux cas, deux requêtes supplémentaires dans la mémoire cache ne sont pas un problème, mais dans une boucle serrée sur un code dont les performances sont critiques, cela peut réduire considérablement les performances.

7 votes

C'est vrai, mais tout code (ou table virtuelle) qui est appelé de manière répétée à partir d'une boucle serrée subira (bien sûr) rarement des manques de cache. En outre, le pointeur de la table virtuelle se trouve généralement dans la même ligne de cache que les autres données de l'objet auxquelles la méthode appelée aura accès, de sorte que nous ne parlons souvent que d'une seule absence de cache supplémentaire.

5 votes

@Qwertie Je ne pense pas que ce soit nécessairement vrai. Le corps de la boucle (s'il est plus grand que le cache L1) pourrait "retirer" le pointeur de table virtuelle, le pointeur de fonction et l'itération suivante devrait attendre l'accès au cache L2 (ou plus) à chaque itération.

32voto

Boojum Points 4688

Extrait de la page 44 de Le manuel "Optimizing Software in C++" d'Agner Fog :

Le temps nécessaire pour appeler une fonction membre virtuelle est supérieur de quelques cycles d'horloge à celui nécessaire pour appeler une fonction membre non virtuelle, à condition que l'instruction d'appel de fonction appelle toujours la même version de la fonction virtuelle. Si la version change, vous aurez une pénalité pour mauvaise prédiction de 10 à 30 cycles d'horloge. Les règles de prédiction et de mauvaise prédiction des appels de fonctions virtuelles sont les mêmes que pour les instructions switch...

0 votes

Merci pour cette référence. Les manuels d'optimisation d'Agner Fog sont la référence en matière d'utilisation optimale du matériel.

0 votes

D'après mes souvenirs et une recherche rapide - stackoverflow.com/questions/17061967/c-switch-and-jump-tables - Je doute que ce soit toujours vrai pour switch . Avec des valeurs totalement arbitraires case des valeurs, bien sûr. Mais si tous les case sont consécutives, un compilateur pourrait être en mesure d'optimiser cela en une table de saut (ah, cela me rappelle le bon vieux temps du Z80), qui devrait être (à défaut d'un meilleur terme) en temps constant. Non que je recommande d'essayer de remplacer vfuncs par switch ce qui est ridicule. ;)

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