Bonjour,
Je travaille sur un code de simulation ne se composant que d'une seule classe Model responsable d'à peu près tout les aspects de la simulation.
J'ai entrepris d'implémenter une forme d'héritage car je dois adapter le code à un second type de simulation qui est assez similaire au premier pour en reprendre une bonne partie.
Ma logique est la suivante : il y a des méthodes et des attributs communs aux deux simulations, ils restent dans la classe de base Model (abstraite), les attributs et méthodes spécifiques aux deux simulations se retrouvent dans deux autres classes qui héritent de Model.
J'ai fait une première implémentation avec une classe Model et ModelFille, mon objectif étant de reproduire le fonctionnement actuel avant d'aller plus loin (voir à la fin les deux classes que je voulais implémenter).
J'ai noté un perte de performance de 10%, ce qui est problématique pour moi car la rapidité d’exécution du code est un des sujets centraux de mon projet.
Afin d'identifier le problème, j'ai découpé mes modifications et je les ai testé à la suite jusqu'à voir une dégradation du temps d’exécution.
Je tiens à préciser que j'ai relancé ces tests plusieurs fois et qu'à chaque fois j'obtenais sensiblement les mêmes durées d’exécutions.
Mon point de départ est l'unique classe Model :
Dans mon main, j'instancie un objet Model et j'appelle run() dessus.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 class Model { Model() ~Model() void run(); // Appel runstep dans une boucle sur n steps et output les résultats à une fréquence donnée void runstep(); // Fait avancer la simulation d'un step void methodsCommunes(); // Plusieurs methodes void methodsSimu1() type attributsCommuns_; type attributsSimus1_; }
Ma première modification a été de créer une classe ModelFille vide qui hérite seulement de Model. Son constructeur appelle celui de Model et ne fait rien d'autre, un objet Model et un objet ModelFille devraient donc être équivalents.
Cependant, si au lieu d’instancier un objet Model, j'instancie un objet ModelFille dans le main, j'ai un grain d'environ 5%. Et je le rappelle, j'obtiens ce résultat quand je relance plusieurs fois ces deux cas, celui où j'instancie ModelFille est toujours plus rapide d'environ 5%.
J'avance plus loin dans mes modifications. J'ai rajouté à la classe Model une méthode runstepChaleur() qui contient simplement un bout de runstep(). Toutes les autres parties de runstep() sont communes aux deux simulations mais la partie "chaleur" sera différente. J'ai donc besoin de cette méthode runstepChaleur() qui est appelée en fin de runstep() et qui finira par être virtuelle pure.
Ma classe fille s'est remplie, elle redéfinit maintenant run() et runstep(). Le contenu de ces deux méthodes et identique à celui de Model::run() et Model::runstep().
La classe ModelFille n'a toujours pas d'attributs supplémentaires et la classe Model contient toujours run() et runstep().
Quand j'appelle run sur mon objet ModelFille (dans le main), c'est la suite d'appel suivante qui se produit : ModelFillle::run -> ModelFillle::runstep -> Model::runstepChaleur().
Ici j'ai à peu près les mêmes performances qu'après ma première modification, à savoir environ 5% plus rapide que le cas de départ.
Ce qui va dégrader mes performances est la modification suivante : dans la classe ModelFille, je redéfinie maintenant la méthode runstepChaleur().
Maintenant, quand j'appelle run() dans le main, il se passe : ModelFillle::run -> ModelFillle::runstep -> ModelFillle::runstepChaleur().
Cela est responsable d'une perte de performance de l'ordre de 20% (par rapport au cas initial).
Dans le cas final, je retombe bizarrement à "seulement" une perte de 10% une fois que je rends la classe Model abstraite et que je défini run et runstepChaleur comme virtuelles pures.
Y a t'il une explications pour ce gain, et surtout pour cette perte de performance ? Est ce que quelqu'un a déjà rencontré ces effets ?
Initialement, je l'avais attribué à l'utilisation de méthodes virtuelles mais la plus grosse perte se produit AVANT que je ne passe de méthodes en virtuelle.
Le problème semble venir du moment où ModelFille::runstep() appelle ModelFillle::runstepChaleur() à la place de Model::runstepChaleur()
Quelques détails pour aider à la compréhension :
- dans mes exemples, run() appelle runstep() 1 million de fois et runstep appelle runstepChaleur() une seule fois par appel
- runstep et runstepChaleur sont de "grosses" méthodes qui prenne un certain temps à s'éxecuter
- si je désactive le calcul de la chaleur, mon cas de base (1 seule classe) et mon cas final (1 classe abstraite et une classe fille) s’exécutent à peu près à la même vitesse
- j'utilise OpenMP dans runstep et dans runstepChaleur comme suit, mais quand je désactive OpenMP à la compilation l'effet est toujours présent.
A la fin, j'aimerais que mes deux classes soient de la forme :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 void runstep() { #pragma omp parallel { ... runstepChaleur(); } } void runstepChaleur() { # pragma omp for for (...) }
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 class Model { Model() virtual ~Model() virtual void run() = 0; void runstep(); virtual void runstep() = 0; void methodsCommunes(); // Plusieurs methodes type attributsCommuns_; }Désolé de la longueur du post, j'ai essayé d'aller à l'essentiel tout en donnant assez de détail.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 class ModelFille : public Model { ModelFille () virtual ~ModelFille () virtual void run(); virtual void runstepChaleur(); void methodsSimu1() type attributsSimus1_; }
Merci d'avance !
Partager