IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Architecture pour choix de strategie


Sujet :

C++

  1. #1
    Membre habitué
    Avatar de Aladore
    Profil pro
    Étudiant
    Inscrit en
    Avril 2009
    Messages
    70
    Détails du profil
    Informations personnelles :
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2009
    Messages : 70
    Points : 144
    Points
    144
    Par défaut Architecture pour choix de strategie
    Bonjour à tous!

    J'ai un petit soucis au niveau de la conception d'une application assez petite en taille. En fait je réalise un projet que j'aimerai mettre sur mon CV, et c'est pour cette raison que j'accorde beaucoup d'importance au choix de ma conception.

    L'utilisateur va avoir une interface graphique pour l'exécution d'un algorithme. Il aura la possibilité de choisir entre deux type de stratégies, et il pourra switcher quand il le souhaite: exécution GPU et exécution CPU.

    En fait j'avais déjà une solution: j'ai une classe MyClass<T>, et T serait une classe stratégie, par exemple: OnGPUAlgo et OnCPUAlgo. Le soucis, c'est que je ne vois pas l'intérêt de cette méthode, puisqu'au final si l'utilisateur doit pouvoir switcher, je dois créer les deux classes dès le début de l'application, et avoir un booléen pour savoir quelle instance utiliser, plutôt lourd non ?

    La deuxième solution que j'avais envisagée et la suivante: soit une classe MyClass, et deux autres classes: MyClassGPU et MyClassCPU qui héritent de MyClass. Je trouve cette architecture trop simpliste, et elle ne reflète pas vraiment la réalité. Cependant elle est simple à mettre en oeuvre, et me permetterait d'avoir:
    MyClass* currentClass;
    MyClassGPU* GPU;
    MyClassCPU* CPU;
    et par exemple: currentClass = GPU;

    Mais bon, encore une fois je crée les deux objets dès le début. Du coup, laquelle est mieux, la première ou la deuxième ? Si aucune, avez-vous une autre idée ?

    Je vous remercie d'avance, et je m'excuse si je me suis mal fait comprendre.

  2. #2
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 619
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 619
    Points : 30 642
    Points
    30 642
    Par défaut
    Salut,

    Tu devrais déléguer un peu mieux les responsabilités...

    En effet, la gestion même de la stratégie devrait... échoir à une classe particulière.

    Ainsi, tu pourrais envisager, effectivement, d'avoir une hiérarchie de classes dans laquelle tes classes MyClassGPU et MyClassCPU héritent, effectivement, d'une classe classe commune.

    Mais tu devrait, en plus, créer un gestionnaire pour les instances de ces classes.

    Tu n'auras, selon ce que tu explique, à un instant T, jamais qu'une instance de MyClassGPU OU une instance de MyClassCPU et, si l'utilisateur décide de changer de stratégie, tu devra de toutes manières veiller à... détruire l'instance en cours avant de passer à l'autre.

    Le tout, en évitant autant que possible de devoir effectuer un test sur deux pointeurs différents ou sur la variable qui permet de garder le choix de l'utilisateur à chaque fois que tu voudra... accéder à la stratégie active.

    Au final, tu aurais sans doute quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // la classe de base, présente l'interface utile aux deux stratégies
    class MyClass
    {
        public:
            /* tout ce qu'il faut */
        private:
            /* empêcher la copie et l'affectation de l'instance
             * on déclare le constructeur par copie et l'opérateur d'affectation
             * en privé SANS LES DEFINIR, en attendant C++1x
             */
            MyClass(MyClass const &);
            MyClass & MyClass(MyClass const &);
    };
    class MyClassCPU
    {
        public:
            /* tout ce qu'il faut */
    };
    class MyClassGPU
    {
        public:
            /* tout ce qu'il faut */
    };
    class Manager
    {
        public:
            enum strategie
            {
                unset,
                cpu,
                gpu
            };
            Manager(): inst_(NULL),actual_(unset){}
            ~Manager(){delete inst_;}
            void create(strategie s/*, paramètres éventuels */)
            {
                // si l'instance actuelle ne correspond pas
                if(actual_!=s)
                {
                    // on détruit l'instance actuelle
                    delete inst_;
                    // et on en crée une selon les besoins de l'utilisateur
                    if(s==cpu)
                        inst_ = new MyClassCPU(/* paramètres éventuels */);
                    else
                        inst_ = new MyClassGPU(/* paramètres éventuels */);
                     // sans oublier de mettre à jour le type de stratégie
                     actual_ = s;
                }
            }
            MyClass /* const */ & instance() /* const */
            {
                if(!inst_)
                    throw InstanceUnset();
                return *inst_;
            }
        private:
            MyClass * inst_;
            strategie actual_;
    };
    Le tout serait utilisé avec des fonctions proches de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    Manager::strategie choice()
    {
        std::cout<<"quelle strategie voulez vous utiliser ?"<<std::endl
                 <<"    [C]PU"<<std::endl
                 <<"    [G]PU"<<std::endl;
        char c='\0';
        Manager::strategie s;
        while(c!='C' && c!='c' && c!='G' && c!='g')
        {
            std::cout<<" Votre choix : ";
            std::cin>>c;
            switch c
            {
                case 'c' :
                case 'C' :
                    s = Manager::cpu;
                    break;
                 case 'g':
                 case 'G':
                    s = Manager::gpu;
                    break;
                  default:
                    s = Manager::unset;
            }
        }
        return s;
    }
    int main()
    {
        Manager man;
        /* quand tu veux changer de stratégie */
        Manager::strategie s=choice();
        man.create(s /* , paramètres éventuels */);
        /* quand tu veux simplement accéder à l'instance */
        MyClass /* const */& inst = man.instance();
        inst.machinChose();
        return 0;
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre habitué
    Avatar de Aladore
    Profil pro
    Étudiant
    Inscrit en
    Avril 2009
    Messages
    70
    Détails du profil
    Informations personnelles :
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2009
    Messages : 70
    Points : 144
    Points
    144
    Par défaut
    Hello, merci beaucoup koala01 pour ta réponse!

    Tu devrais déléguer un peu mieux les responsabilités...
    J'ai bien conscience que niveau conception, je suis vraiment nul! D'ailleurs en passant, t'aurais un livre qui me permettrait d'améliorer mon niveau en conception ? J'ai le livre "Modern Design C++", mais je n'ai pas encore eu le temps de le lire.

    Ta solution est plus qu'intéressante, cependant je me posais une question. Je vais utiliser Cuda pour les calculs sur GPU, et je vais créer, à chaque instances MyClassGPU, une carte de 10000x10000 éléments. Est-ce que ça ne va pas poser des problèmes performances si je crées à chaque changement de stratégie 10000x10000 éléments sur le GPU ou sur le CPU ?

    Je me disais qu'une fois créées, les instances pourraient être gardées en mémoire, et faire une méthode permettant de synchroniser deux instances ... Mais ça me semble pas naturel.

    Je pense dans un premier temps partir sur ta solution, en réfléchissant au problème que j'ai soulevé histoire de voir si je peux améliorer le tout.

  4. #4
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Le but est de pouvoir changer de stratégie rapidement ?

    Alors dans ce cas, tu as effectivement peut-être intérêt à les conserver et ne pas les détruire lors de ton changement de stratégie, mais les mettre en inactif.

    Pour reprendre le modèle de koala, on aurait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class Manager
    {
      ...
        private:
            MyClass * instCourant_;
            strategie actual_;
            std::map<strategie,MyClass*> _instances;
    };
    Quand tu changes de strategie, tu regardes dans la map si elle existe déjà, sinon, tu la crées. Chaque stratégie n'est créée qu'une fois au maximum, et n'est pas créée si elle n'est pas utilisée.

    Et SURTOUT, dans le destructeur, tu pense bien à détruire tes _instances (ici, il n'y en aurait que 2, donc on aurait pu se passer de la map, mais bon...).

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 619
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 619
    Points : 30 642
    Points
    30 642
    Par défaut
    En première analyse je dirais que l'on peut se trouver face à trois cas d'utilisation clairement distincts, et que c'est ton analyse des besoins qui devrait te permettre de déterminer lequel est le mieux adapté à ta situation propre:

    1- Tu peux vouloir travailler avec un jeu de données différent à chaque fois que tu change de stratégie

    2- Tu peux vouloir travailler avec un jeu de données différent en fonction de la stratégie choisie, mais en gardant le même jeu de données si, d'aventure, tu fais un aller / retour entr les stratégies ( GPU-->CPU-->GPU ou CPU-->GPU-->CPU), peut être parce que le type de données manipulé est différent en fonction de la stratégie sélectionnée

    3- Tu peux enfin vouloir toujours travailler avec le même jeu de données, indépendamment de la stratégie sélectionnée.

    Dans le premier cas, tu auras, effectivement, construction (et destruction du jeu précédant) chaque fois que tu change de stratégie : l'idéal est, sans doute, que le jeu de données fasse partie intégrante de la stratégie (c'est le cas que tu sembles avoir envisagé sur base de ma réponse )

    Dans le deuxième cas, si les ressources du système le permettent, tu créerais un jeu de données par stratégie directement dans le gestionnaire (ou du moins, de manière à ce qu'ils soit accessible par le gestionnaire) et tu vérifierais, lors du changement de stratégie, si le jeu de données a déjà été chargé.

    Si ce n'est pas le cas, tu charge le jeu de données avant de construire la stratégie, en rajoutant une référence sur le jeu de données à la stratégie, et, si c'est le cas, tu construit directement la stratégie.

    Cela donnerait un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    /* je considère par défaut que ton jeu de données tient dans un 
     * std::vector ;)
     */
    class MyClass
    {
        public:
            virtual ~MyClass(){}
        /* ce qui convient */
    };
    class MyClassGPU : public MyClass
    {
        public:
            MyClassGPU(std::vector<DataGPU>& d):data_(d){}
        /* ce qui convient */
        private:
            std::vector<DataGPU> & data_;
    };
    class MyClassCPU : public MyClass
    {
        public:
            MyClassCPU(std::vector<DataCPU>& d):data_(d){}
        /* ce qui convient */
        private:
            std::vector<DataCPU> & data_;
    };
    class Manager
    {
     
        public:       
            void create(strategie s/*, paramètres éventuels */)
            {
                // si l'instance actuelle ne correspond pas
                if(actual_!=s)
                {
                    // on détruit l'instance actuelle
                    delete inst_;
                    // et on en crée une selon les besoins de l'utilisateur
                    if(s==cpu)
                    {
                       if(cpu_.empty()) // si le jeu de données est vide
                       {
                           // remplissage du jeu de données CPU
                       }
                        inst_ = new MyClassCPU(cpu_);
                    }
                    else
                    {
                       if(gpu_.empty()) // si le jeu de données est vide
                       {
                           // remplissage du jeu de données GPU
                       }
                        inst_ = new MyClassGPU(gpu_);
                     }
                     // sans oublier de mettre à jour le type de stratégie
                     actual_ = s;
                }
            }
            // le reste ne change pas
     
        private:
            std::vector<DataCPU> cpu_;
            std::vector<DataGPU> gpu_;
    };
    Dans le troisième cas enfin, le jeu de données prend également place dans le gestionnaire (ou est accessible depuis le gestionnaire), mais il n'y a qu'un seul jeu de données, qui est transmis aux différentes stratégies.

    Le code serait donc proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    /* je considère par défaut que ton jeu de données tient dans un 
     * std::vector ;)
     */
    class MyClass
    {
        public:
            virtual ~MyClass(){}
        /* ce qui convient */
    };
    class MyClassGPU : public MyClass
    {
        public:
            MyClassGPU(std::vector<Data>& d):data_(d){}
        /* ce qui convient */
        private:
            std::vector<Data> & data_;
    };
    class MyClassCPU : public MyClass
    {
        public:
            MyClassCPU(std::vector<DataCPU>& d):data_(d){}
        /* ce qui convient */
        private:
            std::vector<DataCPU> & data_;
    };
    class Manager
    {
     
        public:       
            void create(strategie s/*, paramètres éventuels */)
            {
                // si l'instance actuelle ne correspond pas
                if(actual_!=s)
                {
                       if(data_.empty()) // si le jeu de données est vide
                       {
                           // remplissage du jeu de données 
                       }
                    // on détruit l'instance actuelle
                    delete inst_;
                    // et on en crée une selon les besoins de l'utilisateur
                    if(s==cpu)
                    {
                        inst_ = new MyClassCPU(data_);
                    }
                    else
                    {
                        inst_ = new MyClassGPU(data_);
                     }
                     // sans oublier de mettre à jour le type de stratégie
                     actual_ = s;
                }
            }
            // le reste ne change pas
     
        private:
            std::vector<Data> data_;
    };
    Il y a peut être d'autres cas d'utilisation auxquels je n'aurais pas pensé en première analyse...

    Quoi qu'il en soit, tu dois analyser les besoins propres à ta situation pour déterminer ce qui est préférable dans ton cas particulier

    Nota: si je ne suis pas trop réticent (comprend: je peux l'envisager si on n'a vraiment pas le choix ) à avoir deux jeux de données distincts, je le suis beaucoup plus à envisager d'avoir... deux stratégies existantes en même temps, surtout si c'est pour qu'il y en ait une qui ne sert pas
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Membre habitué
    Avatar de Aladore
    Profil pro
    Étudiant
    Inscrit en
    Avril 2009
    Messages
    70
    Détails du profil
    Informations personnelles :
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2009
    Messages : 70
    Points : 144
    Points
    144
    Par défaut
    Merci pour vos réponses, c'est toujours utile de voir plusieurs solutions et choix. Je pense que je vais partir sur te première solution Koala01 et donc faire en sorte que si on swap entre le GPU et le CPU, la map est ré-initialisée.

    Bon je pense que je peux mettre le sujet en résolu, et je vous remercie encore d'avoir réfléchit avec moi à ce problème!

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Choix d'architecture pour infrastructure de dev/test
    Par albator1932 dans le forum Virtualisation
    Réponses: 6
    Dernier message: 22/11/2012, 09h25
  2. Réponses: 2
    Dernier message: 22/08/2012, 06h45
  3. Choix d'une architecture pour un projet
    Par rveber dans le forum Windows
    Réponses: 5
    Dernier message: 30/01/2008, 21h44
  4. Choix d'une architecture pour un serveur
    Par Bebert71 dans le forum Développement
    Réponses: 2
    Dernier message: 14/12/2006, 13h48
  5. Avis sur un choix de strategie pour ActionListener.
    Par JMLLB dans le forum Interfaces Graphiques en Java
    Réponses: 6
    Dernier message: 22/11/2006, 10h06

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo