Design pour eviter un virtual...

Design pour eviter un virtual... - C++ - Programmation

Marsh Posté le 06-09-2006 à 18:51:54    

Bonjour à tous,
 
J'ai un problème de design que je n'arrive pas à résoudre comme j'aimerais... Ceci se pose dans le cadre de recherche de performance pour des programmes à caractère scientifique.
Considerons les classes A et B héritées de Base, par exemple, en oubliant les constructeurs & co :

Code :
  1. class Base {
  2. public:
  3.    virtual void do() = 0;
  4. };
  5. class A : public Base {
  6. public:
  7.    void do() { /* do par A */ }
  8. };
  9. class B : public Base {
  10. public:
  11.    void do() { /* do par B */ }
  12. };


Je peux donc écrire

Code :
  1. /* ... code ... */
  2. Base * base;
  3. /* ... code avec test et pis ... */
  4. base = new A; /* ou  base = new A; si je veux */
  5. base.do();


Ok mais on sait que la notion virtual handicape énormément en terme de performance si le do() a lieu dans un boucle par exemple. L'heritage doit rester haut niveau...  
Auriez vous des idées pour garder une écriture générique tout en assurant des perfs pour un cas comme celui ci? Par exemple dans l'esprit des techniques curiously template?  
 
Merci d'avance

Reply

Marsh Posté le 06-09-2006 à 18:51:54   

Reply

Marsh Posté le 06-09-2006 à 19:40:34    

Il y a souvent des moyens d'éviter de dériver, qui est le moyen le moins souple d'ajouter des fonctionnalités à une classe.
La dérivation n'a réellement d'intérêt que si la relation "est un" est vérifiée et on n'est sûr que la hiérarchie de classes n'a aucun risque d'exploser combinatoirement.
 
Sinon, on peut utiliser la composition (pointeurs sur objets en tant que membres de classes), le pattern decorator pour rajouter des fonctionnalités, ou ajouter des propriétés grâce à une bibliothèque de mixins. Cette dernière méthode peut être élégante et performante si bien utilisée. (google est ton ami)


Message édité par el muchacho le 06-09-2006 à 19:47:42

---------------
Les aéroports où il fait bon attendre, voila un topic qu'il est bien
Reply

Marsh Posté le 06-09-2006 à 20:00:42    

ou faire de l'heritage statique avec des templates

Reply

Marsh Posté le 07-09-2006 à 00:08:35    

Merci de ces pistes!! j'enquète... :)

Reply

Marsh Posté le 07-09-2006 à 10:33:49    

Joel F a écrit :

ou faire de l'heritage statique avec des templates


+1


---------------
TriScale innov
Reply

Marsh Posté le 08-09-2006 à 02:03:34    

N'auriez vous pas un exemple simple d'heritage statique avec template?
Merci d'avance!

Reply

Marsh Posté le 08-09-2006 à 14:08:44    

Code :
  1. template< class D >
  2. class Base
  3. {
  4.   public :
  5.   void someMethod();
  6. };
  7. class SomeDerived : public Base<SomeDerived>
  8. {
  9.   public:
  10.   typedef  Base<SomeDerived> base_t;
  11.   SomeDerived() : base_t() {}
  12.   void someMethod()
  13.   {
  14.      base_t::soemMethod();
  15.     // Derived code if needed
  16.   }
  17. };
  18. SoemDerived a;
  19. a.someMethod();


 

Message cité 1 fois
Message édité par Joel F le 08-09-2006 à 14:09:48
Reply

Marsh Posté le 08-09-2006 à 15:21:02    


t'auras beau essayé, s'il faut de la résolution dynamique, ben il la faut. C'est incompatible avec l'usage souhaité.
 
Quant à l'affirmation 'on sait que c'est lent' c'est typique de l'optimisation prématurée basée sur un mythe.


Message édité par Taz le 08-09-2006 à 15:21:20
Reply

Marsh Posté le 08-09-2006 à 15:22:09    

j'ai 2 questions:
quel est le sens de "heritage statique" ?  pour moi c'a na aucun sens, l'heritage est un schema de structure qui est resolu a la compilation
a quoi sert ce modele ?
 
et puis ca ne reponds pas a la question qui est comment ecrire ce genre de code

Code :
  1. /* ... code ... */
  2. Base * base;
  3. /* ... code avec test et pis ... */
  4. base = new A; /* ou  base = new A; si je veux */
  5. base.do();


sans l'inconvenient du cout d'un appel de methode virtuelle (mais qui est franchement negligeable pour peu que la methode fasse autre chose que 1+1, sortez vos profiler) et a ce probleme il n'y a pas de solution.

Reply

Marsh Posté le 08-09-2006 à 15:24:26    


 
c'est quoi "l'heritage statique" ?

Reply

Marsh Posté le 08-09-2006 à 15:24:26   

Reply

Marsh Posté le 08-09-2006 à 15:41:02    

skelter a écrit :

c'est quoi "l'heritage statique" ?


+1


---------------
Töp of the plöp
Reply

Marsh Posté le 08-09-2006 à 15:47:15    

skelter a écrit :

c'est quoi "l'heritage statique" ?

Je pense qu'il vaut mieux parler de polymorphisme statique, plutôt que d'héritage statique.
 
Pour moi le polymorphisme statique est la possibilité pour une classe d'hériter du comportement d'une autre classe, sans pour autant perdre en performance, puisque tout se résout à la compilation grâce aux mécanismes de templates, plutôt qu'à l'exécution avec des vtables.
 
Cela dit, je suis d'accord avec Taz: cela ne remplace pas le dynamique s'il y en a vraiment besoin. Par contre il y a de nombreux cas où on use de l'héritage et du polymorphisme dynamique uniquement pour des besoins de design ; dans certains de ces cas, il pourrait être avantageux d'utiliser des templates.
 
La perte d'efficacité dues aux vtables n'est pas un mythe. Il suffit de faire quelques tests pour voir qu'un appel de fonction virtuelle coûte à peu près 5x plus cher qu'un appel normal. Donc il y a des cas dans lesquels il est utile de se passer des fonctions virtuelles. (il faudrait en savoir un peu plus sur le problème de zboub77 pour savoir si c'est le cas ici)


---------------
TriScale innov
Reply

Marsh Posté le 08-09-2006 à 17:38:51    

... c'est n'importe quoi ...
 
   1.
      /* ... code ... */
   2.
      Base * base;
   3.
      /* ... code avec test et pis ... */
   4.
      base = new A; /* ou  base = new A; si je veux */
   5.
      base.do();  
 
 
(je passe sur le do)
 
OK, un bon compilateur peut faire sans vtable là, mais étant donné un base*, le cas général, il doit passer par la vtable. S'il n'y a pas de résolution, bah il n'y a pas de surcharge possible. Je vois juste de l'enculage de mouche. Si tu mets pas virtual, ça résout rien, ça surcharge rien. "L'héritage statique" c'est de l'héritage sans virtual. Dans un contexte polymorphique ça ne marche pas. Circulez y a rien à voir.

Reply

Marsh Posté le 09-09-2006 à 18:11:30    

yep, donc rien que je n'aurais pas vu...
 
En particuiler, la méthode "polymorphisme statique" ou "heritage statique" est appelée "Curiously Recursive Pattern". Comme dit Skelter, j'utilise ca pour faire quasiment un "1+1" dans le do()... et un très grand nombre de fois....

Reply

Marsh Posté le 09-09-2006 à 18:12:52    

Vivement le mot clé "auto" dans la prochaine norme!! il y aura alors des astuces pour s'affranchir de ces problèmes! :)
 
En tous cas, merci!

Reply

Marsh Posté le 09-09-2006 à 23:27:44    

Taz a écrit :

... c'est n'importe quoi ...

Qu'est-ce qui est n'importe quoi ?
T'es pas d'accord avec ce que j'ai dit :??:


---------------
TriScale innov
Reply

Marsh Posté le 10-09-2006 à 00:51:31    

Bon alors le polymorphisme dynamique (comprenez le bon vieux virtual) c'est le mal ou non ?

Reply

Marsh Posté le 10-09-2006 à 07:40:17    

Citation :


Bon alors le polymorphisme dynamique (comprenez le bon vieux virtual) c'est le mal ou non ?


Non
J'avais posté il y a quelque temps un code de classe Polymère (c'était une blague) qui illustre le fonctionnement des vtables:

Code :
  1. #include <iostream>
  2. // classe polymère parente
  3. class Objet
  4. {
  5.   const char* monnom;
  6.   virtual void maClasse() { cout<<"Objet"<<endl; }
  7.   protected:
  8.   Objet& operator=(const Objet& o)
  9.   { *((char**)this)=*((char**)&o); return *this; }
  10.   public:
  11.   Objet(const char* nom): monnom(nom) {}
  12.   void quiSuisJe() { cout<<"Mon nom est "<<monnom<<", je suis un "; maClasse(); }
  13. };
  14. // deux classes polymères filles
  15. class ObjetA; class ObjetB;
  16. // classe polymère A
  17. class ObjetA: public Objet
  18. {
  19.   void maClasse() { cout<<"ObjetA"<<endl; }
  20.   public:
  21.   ObjetA(const char* nom): Objet(nom) {}
  22.   ObjetA& operator=(const ObjetB& b)
  23.   { Objet::operator=((Objet& )b); return *this; }
  24. };
  25. // classe polymère B
  26. class ObjetB: public Objet
  27. {
  28.   void maClasse() { cout<<"ObjetB"<<endl; }
  29.   public:
  30.   ObjetB(const char* nom): Objet(nom) {}
  31.   ObjetB& operator=(const ObjetA& a)
  32.   { Objet::operator=((Objet& )a); return *this; }
  33. };
  34. int main()
  35. {
  36.   ObjetA a("A" );
  37.   a.quiSuisJe();
  38.   ObjetB b("B" );
  39.   a=b;
  40.   a.quiSuisJe();
  41.   return 0+0==0;
  42. }


 
L'opérateur = de la class Polymère mère caste à l'arrache l'objet en char*. Mais comme la classe contient une fonction virtuelle, elle n'est pas organisée de la même façon. Le tout premier emplacement mémoire est occupé par un pointeur vers la table virtuelle:


[Pointeur vtable][Pointeur char*]


Ce qui fait que l'opérateur =, au lieu d'affecter la chaîne comme laisserait penser son implémentation, change la vtable!!!
Du coup l'affichage de ce programme est le suivant:


Mon nom est A, je suis un ObjetA
Mon nom est A, je suis un ObjetB


Ceci parceque la vtable contient une liste de pointeurs vers les fonctions virtuelles de la classe:


[...][Pointeur vers B.quiSuisJe()]


Toutes les classes de même type contiennent un pointeur vers la même vtable. Ça se complique bien sûr pour les dérivations multiples.
 
Ok, donc pour récapituler, une fonction virtuelle doit passer par une vtable pour être appelée, et la vtable est une table de pointeurs sur fonction. Ce qui nous fait 3 sauts en mémoire au total avant d'arriver au code, et ce qui explique pourquoi on dit que les fonctions virtuelles sont plus lentes.
 
De plus les performances sur un PC se dégradent au niveau assembleur après le deuxième saut:
Appel de fonction: 1 saut, 1 instruction
Pointeur sur fonction: 2 sauts, 1 instruction
Fonction virtuelle: 3 sauts, 2 instructions
 
Mais celà ne veut nullement dire qu'on en a pas besoin et c'est même très pratique pour exploiter le polymorphisme, les multiples dérivations, etc... Il faut juste savoir de quoi on a besoin avant de coder.

Reply

Marsh Posté le 10-09-2006 à 13:33:02    

OK merci pour la précision. Donc il faut choisir la solution appropriée à la problématique (privilégier l'évolutivité ou les perfs)

Reply

Marsh Posté le 10-09-2006 à 13:50:04    

Celà ne va pas à l'encontre des perfs, les fonctions virtuelles sont tout simplement nécessaires dans certains cas, sans quoi il faudrait recréer leur fonctionnalité.

Reply

Marsh Posté le 10-09-2006 à 13:57:59    

nargy a écrit :

Celà ne va pas à l'encontre des perfs


Pas clair. Quand tu parles du nombre de sauts nécessaires pour accéder à la fonction virtuelle référencée par le pointeur de la vtable, tu suggères donc que le nombre d'instructions nécessaires pour exécuter la procédure virtuelle est plus important que pour une procédure non virtuelle. Cela a donc bien un impact sur les performances.
 
Si l'élément de conception que tu mets en oeuvre n'a pas pour objet d'être polymorphique (au sens ou le modèle n'a pas pour vocation d'être étendu), tu tires donc avantage - en terme de performance - d'une solution ne mettant pas jeu la procédure virtuelle.
 
Ou est le défaut dans le raisonnement ?


Message édité par slash33 le 10-09-2006 à 14:01:16
Reply

Marsh Posté le 10-09-2006 à 14:39:33    

Désolé, je n'ai pas dû bien comprendre:
> il faut choisir la solution appropriée à la problématique
> (privilégier l'évolutivité ou les perfs)
qui ne sont équivalents qu'à la condition que la problèmatique soit purement du domaine des perfs ou de l'évolutivité.
 
Si tu en as besoin, les fonctions virtuelles sont avec 1 instruction machine supplémentaire, la manière la plus efficace d'implémenter le polymorphisme.

Reply

Marsh Posté le 10-09-2006 à 17:25:31    

nargy a écrit :

Si tu en as besoin, les fonctions virtuelles sont avec 1 instruction machine supplémentaire, la manière la plus efficace d'implémenter le polymorphisme.


Ouf on se comprend bien donc.  :pt1cable:

Reply

Marsh Posté le 10-09-2006 à 23:39:39    

bon je vais vomir dans mon coin devant tant d'immondices.

Reply

Marsh Posté le 10-09-2006 à 23:56:55    

Citation :


bon je vais vomir dans mon coin devant tant d'immondices.


toujours aussi désagréable Taz.

Reply

Marsh Posté le 11-09-2006 à 09:05:27    

Taz a écrit :

bon je vais vomir dans mon coin devant tant d'immondices.

Tu veux pas préciser un peu ce qui te gêne ?


---------------
TriScale innov
Reply

Marsh Posté le 11-09-2006 à 14:29:35    

Je pense que Taz veut dire que s'il n'y a pas de virtual, il n'y a pas d'heritage et donc pas de polymorphisme... Qu'en est il des données membres? :)

Reply

Marsh Posté le 12-09-2006 à 09:29:48    

zboub77 a écrit :

Je pense que Taz veut dire que s'il n'y a pas de virtual, il n'y a pas d'heritage et donc pas de polymorphisme... Qu'en est il des données membres? :)

Tu peux avoir de l'héritage sans 'virtual'. C'est le polymorphisme dynamique que tu n'as pas sans 'virtual' :o


---------------
TriScale innov
Reply

Marsh Posté le 13-09-2006 à 02:43:15    

franceso a écrit :

Tu peux avoir de l'héritage sans 'virtual'. C'est le polymorphisme dynamique que tu n'as pas sans 'virtual' :o


 
Tu peux aussi avoir du polymorphisme dynamique sans virtual (et même sans héritage),
mais avec beaucoup de #define  :pt1cable:

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed