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 :

itérateur et multi-thread


Sujet :

C++

  1. #1
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut itérateur et multi-thread
    Bonjour,

    J'ai une fonction qui traite les éléments d'une collection en itérant dessus par extraction de ses éléments un par un.
    Voici un exemple de code simplifié:
    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
    struct MyData
    {
    };
     
    class MyRunner
    {
    	std::vector<MyData> f_md_vect;
    public:
    	void init()
    	{
    		// fill f_md_vect with data
    	}
    	void run();
    };
     
    void MyRunner::run()
    {
    	for(auto & md : f_md_vect)
    	{
    		//... traitement d'un élément de la collection ici
    	}
    }
     
    void mainST()
    {
    	MyRunner mr;
    	mr.init();
    	mr.run();
    }
    J'aimerais améliorer les performances de ce code sachant que le traitement de chacun des éléments de la collection est indépendant. Sur base du code qui précède il me suffirait d'appeler la méthode void MyRunner::run() dans autant de threads que voulu.
    Le problème est alors de s'assurer que chaque élément de la collection ne soit traité qu'une seule fois, donc de verrouiller d'une manière ou d'une autre l'itération qui a lieu dans le code for(auto & md : f_md_vect). En d'autres termes, il faudrait extraire l'itérateur de la boucle for et en faire une variable partagée entre les threads.
    Voici une solution:
    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
    struct MyData
    {
    };
     
    class MyRunnerMT
    {
    	std::vector<MyData> f_md_vect;
    	std::vector<MyData>::iterator f_md_vect_it{}; // null iterator ... (?)
    	std::shared_mutex shmtx;
     
    	MyData *next()
    	{
    		std::lock_guard<std::shared_mutex> lock{shmtx};
    		if (f_md_vect_it == f_md_vect.cend())
    			f_md_vect_it = std::vector<MyData>::iterator{}; // null iterator ... (?)
    		if (f_md_vect_it == std::vector<MyData>::iterator{})
    			return nullptr;
    		return &*f_md_vect_it++;
    	}
    public:
    	void init()
    	{
    		// fill f_md_vect with data
    		if (f_md_vect.size())
    			f_md_vect_it = f_md_vect.begin();
    	}
    	void run();
    };
     
    void MyRunnerMT::run()
    {
    	for (MyData *mdptr; (mdptr = next()) != nullptr;)
    	{
    		MyData & md = *mdptr;
    		//... traitement d'un élément de la collection ici
    	}
    }
     
    void mainMT()
    {
    	MyRunnerMT mr;
    	mr.init();
    	std::future<void> f1 = std::async(&MyRunnerMT::run, &mr);
    	std::future<void> f2 = std::async(&MyRunnerMT::run, &mr);
    	std::future<void> f3 = std::async(&MyRunnerMT::run, &mr);
    	std::future<void> f4 = std::async(&MyRunnerMT::run, &mr);
    	f1.wait();
    	f2.wait();
    	f3.wait();
    	f4.wait();
    }
    Cette solution fonctionne et je l'ai implémentée.
    Mais je me demandais s'il n'y avait pas plus simple et plus lisible.
    Citons ce qui me déplaît:
    -exposer aussi clairement un pointeur (retour de méthode next());
    -initialiser un itérateur avec la valeur "nulle" et en faire la valeur de fin d'itération;
    -le déréférencement de l'itérateur pour en extraire le pointer sous-jacent (&*);
    -utiliser un mutex alors que l'itérateur pourrait être atomic (mais je ne vois pas comment faire).

    Merci pour vos idées

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

    Informations professionnelles :
    Activité : aucun

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

    Ne serais tu pas dans le cadre idéal pour utiliser std::for_each, dans sa version "multi threaded" ?

    Cela pourrait ressembler à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    void MyRunner::run(){
    std::for_each(std::execution::par, f_md_vect.begin(), f_md_vect.end(),[&](MyData /* const */ & d){
        /* manipulation de chaque élément*/;
    });
    }
    (C++17 inside !!! )
    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
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Ou lancer plusieurs threads en leur donnant chacun un morceau de la collection à traiter. Sous forme de pointeur où commencer et d'un nombre d'objet à traiter.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  4. #4
    Invité
    Invité(e)
    Par défaut
    Salut
    Je n'ai peut-être pas bien compris mais pourquoi ne pas tout simplement utiliser OpenMP pour traiter ta collection en parallèle ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void MyRunner::run(){
        #pragma omp parallel for
        for (unsigned i=0; i<md_vect.size(); i++) {
            /* manipulation de chaque élément*/;
        }
    }

  5. #5
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    C'est ce que fait std::for_each avec std::execution::par (au moins pour libstdc++).

  6. #6
    Invité
    Invité(e)
    Par défaut
    Merci pour l'info, je ne connaissais pas cette fonctionnalité.
    Honnêtement, je ne sais pas si je l'utiliserai un jour car openmp est dispo sur pratiquement tous les compilo de moins de 10 ans et permet, si besoin, de gérer finement la parallélisation.

  7. #7
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Merci pour vos réponses.
    Elles me donnent des pistes dans d'autres contextes pour l'avenir. Mais actuellement je suis sous VS2015 et donc limité à un subset de C++14.

    Ensuite, je souhaite paralléliser la méthode run et non pas une boucle for (voir mon exemple avant). Car avant la boucle il y a quelques initialisations à effectuer, et ce, pour chaque thread (ouvrir des connections DB, des sockets, etc).
    J'aimerais aussi contrôler le nombre de threads à ma guise, éventuellement renvoyer une valeur de retour en fin d'exécution du thread.
    Il faudrait aussi que les éléments de la collection soient traités de manière non déterministe. Je veux dire par là que la solution de Bousk visant à découper la collection en part égales n'est pas optimale car rien ne prédit que le traitement de chaque élément sera constant dans le temps (en réalité c'est même très variable).

    Revoici la méthode de mon exemple dans laquelle je cherche à rendre un itérateur sur collection thread-safe.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void MyRunner::run()
    {
    	init_db();
    	init_socket();
    	// more init...
    	for(auto & md : f_md_vect) // <-- cet itérateur implicite doit être thread-safe
    	{
    		//... traitement d'un élément de la collection ici
    	}
    }
    Encore merci

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par SimonDecoline Voir le message
    Merci pour l'info, je ne connaissais pas cette fonctionnalité.
    Honnêtement, je ne sais pas si je l'utiliserai un jour car openmp est dispo sur pratiquement tous les compilo de moins de 10 ans et permet, si besoin, de gérer finement la parallélisation.
    C'est toujours le même problème : préfères tu te taper à la main les N instructions rendues nécessaires par une bibliothèque "bas niveau", au risque d'en oublier, d'en mélanger l'ordre logique ou de te tromper dans les paramètres que tu leur fournis, ou utiliser une fonctionnalité fournie par une bibliothèque plus "haut niveau", qui t'assure que tout sera exécuté dans le bon ordre, avec les bons paramètres

    Si, en plus, la bibliothèque haut niveau peut te garantir que, grâce au paradigme utilisé (le paradigme générique dans le cas présent), le compilateur sera en mesure de faire des optimisations que tu auras difficile à reproduire autrement, et que la fonctionnalité haut niveau sera donc "aussi efficace que possible", on peut décemment se poser la question de savoir pourquoi s'en priver non

    Je ne nie pas l'intérêt qu'il peut y avoir à s'intéresser à la bibliothèque "bas niveau", ou mieux encore, à être en mesure de l'utiliser correctement. Mais il ne faut pas non plus nier l'énorme avantage -- entre autres en termes de lisibilité du code, ou, de la possibilité qui nous est offerte de rester concentré sur notre problème réel -- qu'il y a à utiliser la fonctionnalité "haut niveau"
    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

  9. #9
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par camboui Voir le message
    Merci pour vos réponses.
    Elles me donnent des pistes dans d'autres contextes pour l'avenir. Mais actuellement je suis sous VS2015 et donc limité à un subset de C++14.

    Ensuite, je souhaite paralléliser la méthode run et non pas une boucle for (voir mon exemple avant). Car avant la boucle il y a quelques initialisations à effectuer, et ce, pour chaque thread (ouvrir des connections DB, des sockets, etc).
    J'aimerais aussi contrôler le nombre de threads à ma guise, éventuellement renvoyer une valeur de retour en fin d'exécution du thread.
    Il faudrait aussi que les éléments de la collection soient traités de manière non déterministe. Je veux dire par là que la solution de Bousk visant à découper la collection en part égales n'est pas optimale car rien ne prédit que le traitement de chaque élément sera constant dans le temps (en réalité c'est même très variable).
    Justement OpenMP est fait pour gérer ce genre de problème. Par exemple, le code suivant crée des threads, permet de faire des initialisations spécifiques puis parallélise une boucle dans une fonction, dynamiquement.

    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
    #include <iostream>
     
    void run() {
    // répartit la boucle dynamiquement sur les threads déjà créés
    #pragma omp for schedule(dynamic,1)
        for (int i=0; i<8; i++)
            std::cout << "hello\n";
    }
     
    int main() {
     
    // duplique la section de code sur plusieurs threads
    #pragma omp parallel
        {
            // faire ici les initialisations pour chaque threads
            // ...
     
            // lance la fonction, ce qui répartira sa boucle for sur les threads créés
            run();
        }
     
        return 0;
    }
    Pour le nombre de threads à utiliser, on peut le spécifier dans le code ou dynamiquement à l'exécution, Par exemple, pour exécuter sur 2 coeurs :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    OMP_NUM_THREADS=2 ./a.out
    Citation Envoyé par koala01 Voir le message
    C'est toujours le même problème : préfères tu te taper à la main les N instructions rendues nécessaires par une bibliothèque "bas niveau", au risque d'en oublier, d'en mélanger l'ordre logique ou de te tromper dans les paramètres que tu leur fournis, ou utiliser une fonctionnalité fournie par une bibliothèque plus "haut niveau", qui t'assure que tout sera exécuté dans le bon ordre, avec les bons paramètres

    Si, en plus, la bibliothèque haut niveau peut te garantir que, grâce au paradigme utilisé (le paradigme générique dans le cas présent), le compilateur sera en mesure de faire des optimisations que tu auras difficile à reproduire autrement, et que la fonctionnalité haut niveau sera donc "aussi efficace que possible", on peut décemment se poser la question de savoir pourquoi s'en priver non

    Je ne nie pas l'intérêt qu'il peut y avoir à s'intéresser à la bibliothèque "bas niveau", ou mieux encore, à être en mesure de l'utiliser correctement. Mais il ne faut pas non plus nier l'énorme avantage -- entre autres en termes de lisibilité du code, ou, de la possibilité qui nous est offerte de rester concentré sur notre problème réel -- qu'il y a à utiliser la fonctionnalité "haut niveau"
    Merci pour la leçon mais tu devrais peut-être te renseigner un peu au lieu de me prendre pour un étudiant de 1re année. OpenMP n'est pas une bibliothèque mais un ensemble de directives gérées directement par le compilateur et ça permet beaucoup plus de choses que ta micro-fonctionnalité C++17 qui arrive avec 10 ans de retard.
    Dernière modification par LittleWhite ; 26/12/2018 à 16h15.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par SimonDecoline Voir le message
    Merci pour la leçon mais tu devrais peut-être te renseigner un peu au lieu de me prendre pour un étudiant de 1re année.
    Je ne vois pas en quoi je t'ai fait passer pour un étudiant de première année...

    Au contraire, j'ai l'impression que le NIH est beaucoup plus souvent invoqué par les "vieux tromblons" qui le justifient -- justement -- par le fait que "on n'a bien réussi à s'en passer pendant vingt ans, pourquoi changer nos habitudes maintenant".

    Il n'empêche que, quand on a la chance de pouvoir modifier ses habitudes de manières à se simplifier la vie,
    OpenMP n'est pas une bibliothèque mais un ensemble de directives gérées directement par le compilateur
    Même pas!

    C'est avant tout une spécification!

    Ce sont bel et bien des bibliothèques qui fournissent les directives et l'implémentation de cette spécification
    et ça permet beaucoup plus de choses que ta micro-fonctionnalité C++17 qui arrive avec 10 ans de retard.
    Je n'en disconviens absolument pas

    Et quoi je devrais jeter le bébé avec l'eau du bain

    Est-ce parce que la norme décide d'exposer quelque chose avec dix ans de retard que je devrais décider de ne pas utiliser la fonctionnalité qu'elle m'offre

    Ou est ce parce qu'elle ne m'offre qu'une infime partie de ce que permet la spécification OpenMP

    Et si, au lieu d'attendre (peut être vainement d'ailleurs) que toutes les possibilités offertes par OpenMP soient implantées d'une manière ou d'une autre dans la bibliothèque standard pour me décider à utiliser ce qu'elle m'offre déjà, je décidais -- au contraire -- de me jeter sur ce qu'elle m'offre pour me faciliter la vie autant que ce qu'elle peut me permettre

    Je n'ai certainement pas l'intention de t'obliger à te rallier à mon point de vue. Mais moi, vois tu, je revendique le fait d'être fainéant : je ne fais peut-être pas grand chose, mais je m'arrange pour faire les choses correctement, de manière à ne pas devoir repasser dessus par la suite.

    Et donc, si j'ai la possibilité de "laisser faire" (mon compilateur ou la bibliothèque standard) correctement à ma place quelque chose que je risquerais de ne pas faire "aussi correctement", crois bien que je saute sur l'occasion, et que "je laisse faire" ...

    Tu peux avoir un avis tout à fait différent, et je n'ai aucun droit de te demander d'en changer

    Mais j'ai parfaitement le droit d'exprimer un avis, fusse-t-il en désaccord avec le tien
    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

  11. #11
    Invité
    Invité(e)
    Par défaut
    C'est vraiment le monde à l'envers. Je dis que je préfère utiliser OpenMP car il est plus répandu et plus puissant. Là dessus tu parles de "problème" en m'expliquant des banalités sur un ton condescendant et maintenant c'est moi qui essaierais de t'empêcher d'utiliser le merveilleux for_each parallèle de C++17... Mais tu fais bien ce que tu veux, j'en ai absolument rien à faire.

    Pourquoi ne réponds-tu pas plutôt au dernier message de Camboui sur le vrai sujet de la discussion au lieu d'enfoncer des portes ouvertes sur les bibliothèques bas niveau ou haut niveau ?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par SimonDecoline Voir le message
    C'est vraiment le monde à l'envers. Je dis que je préfère utiliser OpenMP car il est plus répandu et plus puissant. Là dessus tu parles de "problème" en m'expliquant des banalités sur un ton condescendant
    Où vois tu la moindre trace de condescendance dans ce que j'ai écrit

    J'émets un avis -- que je ne t'oblige absolument pas à accepter, mais que j'apprécierais néanmoins que tu respecte, comme je respecte le tien -- selon lequel, il n'y a rien à faire : si tu dois suivre une procédure prenant N étapes en compte (ou N est plus grand que 1), il y a de grand risques pour que tu te vautres en essayant de les suivre.

    J'appellerai cela "réalisme", d'autres appelleront cela "fatalisme" si cela leur chante, mais pas condescendance

    et maintenant c'est moi qui essaierais de t'empêcher d'utiliser le merveilleux for_each parallèle de C++17... Mais tu fais bien ce que tu veux, j'en ai absolument rien à faire.
    Mais n'est ce pas toi qui vient de reprocher le fait que c'est arrivé avec dix ans de retard, et que cela n'apporte qu'une infime partie de ce que permet OpenMP

    Hé bien, sache que, pour moi, mieux vaut tard que jamais! Et que je préfères malgré tout me dire "bah, std::for_each (et d'autres fonctions) me permettent de définir facilement des politiques multi-threadées de manière facile, sans avoir à m'inquiéter des détails lugubres de la manière dont elle s'y prend, et je compte bien en profiter".

    Car tout le problème est là : tous les détails lugubres que ces fonctionnalités prennent en charge, ca fait autant de détails dont je n'ai pas besoin de m'occuper, et cela me va très bien.

    Maintenant, si tu préfères attendre que l'ensemble de OpenMP soit proposé par la bibliothèque standard, ou même la Saint Glinglin avant de commencer à utiliser les fonctionnalités de la bibliothèque standard, je m'en fous royalement!

    Simplement, je n'ai de toutes évidence pas le même point de vue que toi, et j'ai donc -- effectivement -- pris la peine de le justifier. Non pas pour te faire la leçon d'une manière lénifiante ou condescendante, mais bien :
    • parce que cela permet d'ouvrir la discussion (a priori, les gens peuvent comprendre mon point de vue, quitte à exposer un point de mon raisonnement qui serait erroné)
    • parce que cela permet à d'autres (bon, pas à toi, visiblement) de se faire leur propre avis sur base d'une justification qui en vaut bien une autre
    • parce que malgré tout, nous sommes en démocratie, et que j'ai quand même bien le droit d'exposer un avis, quitte à ce qu'il ne soit pas considéré comme valable, non de dieux! (mais, alors, qu'on m'indique où mon raisonnement fait défaut, que je puisse aussi évoluer )
    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

  13. #13
    Invité
    Invité(e)
    Par défaut
    D'accord,c'est très bien, bravo. Et donc là maintenant tu fais comment pour résoudre le problème de Camboui. J'ai donné des éléments de réponse avec OpenMP donc profite de ton droit d'exposer un avis pour nous présenter la solution que tu envisagerais.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Tout simplement :

    A dire vrai, je ne crois pas que Camboui devrait paralléliser le run; qu'il faut intercaler quelque chose entre le runner et les données

    Et je ne crois pas qu'il devrait parralléliser le run.

    Ce "quelque chose" correspondrait à la partie réellement parallélisable de la logique, et serait essentiellement composé :
    1. de l'intervalle des éléments que chaque traitement parallèle devra traiter et
    2. de la logique qui doit être parallallélisée

    Cela pourrait ressembler à 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
    class ParallelPart{
    using iterator = typename Runner::iterator;
    public:
         ParallelPart(iterator begin, iterator end):begin_{begin}, end_{end}{
         }
         void execute(){
              init();
              auto temp = begin_; // juste pour ne pas perdre l'interval
              while(temp != end_){
                  /* ce qu'il faut faire */
                  ++temp;
              }
         }
    private:
        void init(); // les initialisations qui doivent être parallélisées
        iterator begin_;
        iterator end_;
    };
    Avec cet élément "intercalaire", Camboui pourrait permettre à l'utilisateur de choisir lui-même le nombre de processus parallèles qui sont exécutés, sous une forme qui serait 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
    class Runner{
    private:
        std::vector<MyData> f_md_vect;
        std::vector<ParallelPart> jobs_;
    public:
        using iterator = typename std::vector<MyData>::iterator; // pour faciliter la définition dans ParallelPart
        void defineParalllels(size_t count = 1){
            assert(count > 0 && "at least one job required");
            jobs.clear();
            auto count = f_md_vect.size() / jobs;
            auto left = f_md_vect.size();
            auto beg = f_md_vect.begin();
            auto end = f_md_vect.end();
            while(it!= end){
                jobs.emplace_back(ParallelPart(beg, left>count? beg + count : end);
                left-= count;
                beg+= left>count ? count : left;
           }
        }
        void run(){
            /* pour le cas où le nombre de processus parallèles n'a pas été défini */
            if(jobs.empty())
                defineParalllels();
            /* les initialisation  non parallèles viennent ici (s'il y en a) */
            for(auto & it : jobs){
                td::async(std::launch::async,[&](){it.execute();});
            }
        }
    }
    Bien sur, ce code n'a pas été testé, mais le principe est là

    Seulement, on en revient toujours au même point : si l'utilisateur est responsable de l'appel de deux étapes d'une procédure, on doit s'attendre à ce qu'il en oublie une (c'est d'ailleurs la raison pour laquelle j'ai déjà ajouté un test dans run).

    Je proposerais donc d'ajouter un paramètre à la fonction run et d'adapter le début de la fonction sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
        void run( size_t process = 1){
            /* pour le cas où le nombre de processus parallèles n'a pas été défini */
            if(process!=1 || jobs.size()!= process)
                defineParalllels(process);
            /* la suite ... */
    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

  15. #15
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Tout simplement :
    ...
    Effectivement c'est beaucoup plus simple que d'ajouter 2 directives openmp...

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Il y a juste un truc que tes deux malheureuses directive OpenMP ne feront malheureusement pas : respecter une règle totalement idiote appelée SRP (Single Responsability Principle)

    Cette règle nous dit en simplifié que
    un nom (groupe nominal) == un type de donnée ou une donnée, un verbe (groupe verbal) == une fonction
    Il apparaît clairement dans l'analyse des besoins que tu as besoin de "quelque chose" qui puisse être parallélisé il faut donc quelque chose dans le code, qui n'a rien à avoir avec MyRunner ni avec MyData, qui puisse représenter cette notion.

    Non pas parce que j'espère pouvoir la réutiliser ailleurs (quoi que, à partir du moment où elle existe, je n'aie aucune raison de ne pas l'utiliser à chaque fois que j'en ai besoin), mais bien parce qu'elle me permet de séparer très clairement ce qui doit être parallélisé de ce qui ne peut pas l'être.

    Après tout, comme disait l'autre:
    tout problème peut être résolu par l'ajout d'un niveau d'indirection... sauf, bien entendu, un nombre trop élevé d'indirection
    Hé bien, je n'ai rien fait d'autre : j'ai exprimé clairement au travers d'une classe un besoin précis et réel, et j'ai modifié le code pour qu'il utilise cette classe de manière naturelle et efficace
    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

  17. #17
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Il faudrait aussi que les éléments de la collection soient traités de manière non déterministe. Je veux dire par là que la solution de Bousk visant à découper la collection en part égales n'est pas optimale car rien ne prédit que le traitement de chaque élément sera constant dans le temps (en réalité c'est même très variable).
    Dans ce cas, tu devrais lancer tes threads, puis chaque thread récupère un élément à traiter quand il a fini le précédent. Tu as juste besoin d'un lock dans cette fonction et pas besoin d'itérateur alambiqué.
    Tu pourrais même n'avoir qu'un entier atomic au lieu d'un lock.
    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
     
    template<class T>
    class MyRunnerMT
    {
    public:
      MyRunnerMT(std::vector<T>& data) : mData(data) {}
      T* GetNext()
      {
        unsigned int index = mNext++;
        if (index < mData.size())
          return &(mData[index]);
        return nullptr;
      }
     
      void Run(unsigned int nbThreads)
      {
        mThreads.reserve(nbThreads);
        for (unsigned int i = 0; i < nbThreads; ++i)
          mThreads.emplace_back(std::thread([this]()
          {
            while (T* item = GetNext())
              item->Process();
          }));
     
        for (std::thread& t : mThreads)
          t.join();
      }
     
    private:
      std::vector<T>& mData;
      std::atomic<unsigned int> mNext{0};
      std::vector<std::thread> mThreads;
    };
     
    std::vector<Foo> data;
    MyRunnerMT<Foo> mr(data);
    mr.Run(4);
    À vue de nez, non testé.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  18. #18
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Et bien merci à tous deux

    Et inutile de s'empoigner
    Ça me permet de découvrir deux moyens pour obtenir des résultats semblables.
    Le premier est descriptif (SimonDecoline), le deuxième est procédural (Koala01).

    Perso, et parce que je suis sans doute un vieux dinosaure, je me sens plus à l'aise dans un contexte procédural.
    Il me permet de "voir" et "comprendre" (avec des gros guillemets ) le code généré et l'exécution qui en découlera. J'ai l'impression de mieux maîtriser ce que je souhaite obtenir.
    Les directives sont indéniablement plus concises, mais aussi plus obscures et ne font pas partie des spécifications C++ proprement dites (si j'ai bien compris). Les soucis ou erreurs de codage sont peut-être aussi moins simples à débusquer.
    Certes, elles sont une extension proposée aux éditeurs de compilateurs C++ qui, effectivement, semblent l'implémenter depuis un certain temps déjà.

    Merci encore pour vos deux solutions que je vais regarder en détail et tester dans des cas simples (avec un petit benchmark ).

    Koala01, il y a juste un aspect qui ne me convient pas dans ta proposition, et je l'ai dit dans ma première réponse en citant Bousk, c'est de déterminer à l'avance l'intervalle des éléments que chaque thread devra traiter. C'est prendre pour acquis que chaque élément sera traité en temps constant. Dans mon cas c'est faux, et d'une manière plus générale je pense que c'est une mauvaise hypothèse.

  19. #19
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Et entre temps, formidable , Bousk intervient et réagit à ce qui, justement, me chipote
    Merci !

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

Discussions similaires

  1. Tri multi-threadé
    Par Tifauv' dans le forum C
    Réponses: 8
    Dernier message: 28/06/2007, 09h00
  2. Réponses: 2
    Dernier message: 15/05/2004, 18h33
  3. Réponses: 16
    Dernier message: 30/01/2004, 11h05
  4. [VB6][active x] faire du multi-thread avec vb
    Par pecheur dans le forum VB 6 et antérieur
    Réponses: 9
    Dernier message: 20/05/2003, 12h01
  5. [Kylix] exception qtinft.dll et multi-threading
    Par leclaudio25 dans le forum EDI
    Réponses: 3
    Dernier message: 27/03/2003, 18h09

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