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

Threads & Processus C++ Discussion :

threads de travail


Sujet :

Threads & Processus C++

  1. #1
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut threads de travail
    Bonjour à tous,

    j'aimerai lancer plusieurs traitements répétés de complexité identiques en parallèle (en général 1, 2 ou 3) pour une appli temps-réel.
    La création de thread étant couteuse, j'imagine qu'il vaut mieux tout créer au début du programme !

    Supposons que j'aie dans ma classe de base de calcul un vector de thread.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Engine
    {
    //
    private:
    	CFPSCounter                                   m_fps_counter;
    	std::vector<boost::shared_ptr<boost::thread>> m_execution_threads;
    };
    Je créé ensuite autant de threads que de traitements (2 par exemple).
    Toutes les 20 ms, il faut que je relance les 2 traitements en parallèle.
    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    m_execution_threads[0].reset(new boost::thread(
    		boost::bind(&IVisionModule::Apply, m_vision_chains[0])));
    m_execution_threads[1].reset(new boost::thread(
    		boost::bind(&IVisionModule::Apply, m_vision_chains[1])));
    Du coup, c'est pas terrible : ça risque d'être lent mon affaire... Le new me recréé un thread à chaque fois...

    Vous avez une idée pour donner juste du travail à des threads?

    P.S : J'ai pensé à boost::asio qui émule l'asynchronicité via un pool de thread (boost::asio::post() ), mais je sais pas si c'est bien performant tout ça. J'ai une limite de "souplesse" à échanger contre de la performance...


  2. #2
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,
    Il me semble qu'il y a un pattern dans ACE qui pourrait faire ton affaire. Je crois que c'est l'active object pattern.
    En version hyper simplifié, l'idée c'est que ton thread fonctionne comme une boucle de message. Il est en attente qu'une commande soit enfilée dans la boucle. Puis il la sort, l'exécute et se remet en attente d'une nouvelle commande. En fait, le thread n'est jamais quitté.

  3. #3
    Inactif  
    Avatar de Mac LAK
    Profil pro
    Inscrit en
    Octobre 2004
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Âge : 49
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Octobre 2004
    Messages : 3 893
    Points : 4 846
    Points
    4 846
    Par défaut
    Pour ça, j'associe en général un sémaphore à chaque thread, plus une adresse mémoire de passage d'un bloc de données.
    Au signalement, le thread travaille sur le bloc fourni, retourne le résultat dans une zone prévue et lève un signal quelconque (flag, sémaphore, tout dépend du contexte). Il est assez difficile de faire plus rapide, vu que l'on réduit les opérations inutiles à un maximum presque absolu.

    Une autre variante est d'alimenter par une FIFO un pool de threads avec des données à traiter, les résultats étant renvoyés dans une autre FIFO. Ce mécanisme fonctionne plutôt bien avec des volumes importants de données.
    Mac LAK.
    ___________________________________________________
    Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.

    Sources et composants Delphi sur mon site, L'antre du Lak.
    Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.

    Rejoignez-nous sur : Serveur de fichiers [NAS] Le Tableau de bord projets Le groupe de travail ICMO

  4. #4
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Salut,
    Il me semble qu'il y a un pattern dans ACE qui pourrait faire ton affaire. Je crois que c'est l'active object pattern.
    En version hyper simplifié, l'idée c'est que ton thread fonctionne comme une boucle de message. Il est en attente qu'une commande soit enfilée dans la boucle. Puis il la sort, l'exécute et se remet en attente d'une nouvelle commande. En fait, le thread n'est jamais quitté.
    Salut 3DArchi, j'étais justement tombé sur ce pattern quelques jours auparavant : http://blog.cplusplus-soup.com/2006/12/boost.html
    Cet exemple utilise Boost.Asio. Je sais pas si c'est le plus indiqué pour du temps réel, mais bon.

    Citation Envoyé par Mac LAK Voir le message
    Pour ça, j'associe en général un sémaphore à chaque thread, plus une adresse mémoire de passage d'un bloc de données.
    Au signalement, le thread travaille sur le bloc fourni, retourne le résultat dans une zone prévue et lève un signal quelconque (flag, sémaphore, tout dépend du contexte). Il est assez difficile de faire plus rapide, vu que l'on réduit les opérations inutiles à un maximum presque absolu.

    Une autre variante est d'alimenter par une FIFO un pool de threads avec des données à traiter, les résultats étant renvoyés dans une autre FIFO. Ce mécanisme fonctionne plutôt bien avec des volumes importants de données.
    Pour associer un sémaphore à chaque thread, j'imagine que tu es dépendant de l'OS? Je connais encore mal cette façon de programmer (mes souvenirs de sémaphore remonte 6 ans en arrière quand j'avais fait un peu de VxWorks à l'école, je n'ai jusqu'à présent utilisé que les mutex dans mon boulot professionel), alors j'ai un peu de mal à me faire une idée. Si tu pouvais m'envoyer un mot clé ou un lien vers lequel je puisse me tourner pour me documenter ce serait sympa.
    Le pool de threads avec FIFO, je vois beaucoup mieux ce que ça représente. J'ai trouvé une implémentation avec threadpool (Boost non officielle). Le principe est de choisir un nombre de threads de travail à l'initialisation. Puis de leur donner du boulot. La politique peut etre changé (FIFO, LIFO, priority). Moi ce serait plutôt FIFO ici, comme tout va vraiment se passer en parallèle.
    L'active object avec Boost.Asio pourrait faire l'affaire. J'avoue ne pas avoir d'idées concernant la performance.

    Surtout, si vous avez des idées, des remarques sur ce que je viens de dire, n'hésitez pas !

  5. #5
    Inactif  
    Avatar de Mac LAK
    Profil pro
    Inscrit en
    Octobre 2004
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Âge : 49
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Octobre 2004
    Messages : 3 893
    Points : 4 846
    Points
    4 846
    Par défaut
    Citation Envoyé par poukill Voir le message
    Pour associer un sémaphore à chaque thread, j'imagine que tu es dépendant de l'OS? Je connais encore mal cette façon de programmer (mes souvenirs de sémaphore remonte 6 ans en arrière quand j'avais fait un peu de VxWorks à l'école, je n'ai jusqu'à présent utilisé que les mutex dans mon boulot professionel), alors j'ai un peu de mal à me faire une idée. Si tu pouvais m'envoyer un mot clé ou un lien vers lequel je puisse me tourner pour me documenter ce serait sympa.
    Non, pas du tout au contraire. J'utilise des librairies d'abstraction à ce niveau, comme ACE ou POCO, qui fournissent une interface commune quel que soit l'OS. Voilà la page des sémaphores pour POCO, par exemple.

    En fait, je définis dans une structure toutes les données nécessaires au thread pour pouvoir bosser (sémaphores, flags, buffers, instances de classes, etc.), sans recopies bien entendu, et c'est cette structure que je passe au thread en paramètre. Chaque thread a besoin de deux sémaphores : un qui lui est spécifique, et un global pour la distribution du boulot.
    A l'initialisation, le thread-père va initialiser la structure, lancer le thread et attendre le sémaphore propre du thread.
    Le thread, lui, va si nécessaire copier la structure (qui peut être sur la pile du thread-père, donc "volatile") dans ses propres variables locales, finir son initialisation, signaler son propre sémaphore (=> libération du père), et ensuite attendre le sémaphore global (= du boulot vient d'arriver).

    Cette technique est un peu lente au niveau de la création des threads (enfin... tout est relatif, hein, on peut en créer quelques milliers en une seule seconde ! ), mais permet ensuite d'avoir des threads immédiatement disponibles pour le travail. En général, j'arrête les threads via un flag global (demande "propre" de terminaison), suivi d'un signalement de tous les sémaphores. Ne reste plus qu'à attendre la fin des threads, avec flinguage brutal après un certain délai si c'est nécessaire.

    Pour l'attente du boulot, c'est très simple : le sémaphore contient autant de jetons que de données en attente, donc si tu pousses 200 "paquets" vers le pool de threads, tu "crédites" le sémaphore de 200. Chaque thread va "consommer" une unité en se libérant, traiter un paquet, puis de nouveau attendre le sémaphore. S'il y a des données, l'attente est nulle. Sinon, le thread va être bloqué jusqu'à réception de nouvelles données. Quand le sémaphore contient zéro jetons, tous les threads sont en attente, et la FIFO source est vide.

    Citation Envoyé par poukill Voir le message
    Le pool de threads avec FIFO, je vois beaucoup mieux ce que ça représente. J'ai trouvé une implémentation avec threadpool (Boost non officielle). Le principe est de choisir un nombre de threads de travail à l'initialisation. Puis de leur donner du boulot. La politique peut etre changé (FIFO, LIFO, priority). Moi ce serait plutôt FIFO ici, commei tout va vraiment se passer en parallèle.
    Le gros problème est de savoir si le résultat des threads doit être "ordonné" ou non : en effet, tu ne peux pas savoir à l'avance quel thread aura terminé en premier.
    Donc, si les paquets "source" doivent être traités de façon à ce que les paquets "résultat" aient le même ordre, il faut parfois utiliser un étage de plus à la sortie des threads pour rétablir l'ordre.
    Pour ma part, j'utilise le plus souvent un numéro séquentiel unique généré lors de la récupération d'un paquet "source", plus une FIFO unitaire de sortie pour chaque thread. Ensuite, un sérialiseur connecte toutes les FIFO des threads, attends "le" bon numéro, et sérialise dans le bon ordre vers la FIFO finale.

    Citation Envoyé par poukill Voir le message
    L'active object avec Boost.Asio pourrait peut être aussi faire l'affaire. J'avoue ne pas avoir d'idées concernant la performance.
    Je sais qu'en général, pour ce genre de choses, je prévois des systèmes très optimisés pour garantir les performances. Les FIFO, par exemple, sont gérées sans mutex, j'use et abuse des fonctions atomiques (type InterlockedExchange, InterlockedIncrement, etc.), et je ne fais bien entendu passer que des pointeurs. Je rends les données indissociables entre elles par des ajouts systématiques de structures d'encapsulation, afin de ne toujours avoir besoin que d'un seul pointeur à manipuler.
    En général, si tu ne mets pas de mutex inutiles, tes performances ne sont conditionnées que par peu d'éléments :
    • Vitesse de commutation de contexte après signalement du sémaphore.
      Bien entendu, les OS temps réel s'en sortent mieux que les OS généralistes à ce niveau. Toutefois, Windows (kernel NT) et Linux (kernel 2.6) offrent en général des performances correctes.
    • Gestion réelle des threads : dépend de l'OS, bien sûr, et aussi du nombre de cœurs disponibles.
      En général, je dimensionne mon pool de threads par rapport au nombre de cœurs, de façon à ne pas avoir de threads "inutiles" tout en ayant une utilisation maximale du CPU.


    Côté objets, un "vrai" pool de threads n'est pas forcément nécessaire (voire souhaitable...) dans un tel contexte : contrairement à l'usage possible sur un serveur, par exemple HTTP ou BDD, il n'y a pas de "pics" de charge à assurer, ni de fluctuations très importantes dans la répartition de la charge. Soit tes threads bossent, soit ils ne foutent rien, mais tu n'as pas de grosses contraintes de répartition à faire. Du coup, un simple tableau d'objets-threads suffit en général amplement.

    L'autre point critique, ce sont les FIFO : il est préférable de bien travailler leurs performances, et de tenter au maximum d'éviter l'utilisation de mutex. Souvent, on peut se passer de mutex sur une des extrémités de la FIFO (soit côté application, soit côté threads de travail) : c'est toujours bon à prendre, et de préférence côté threads de travail.
    Toutefois, cela demande pas mal de boulot de faire ça, et les FIFO "toutes prêtes" sont rarement idéales.
    Mac LAK.
    ___________________________________________________
    Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.

    Sources et composants Delphi sur mon site, L'antre du Lak.
    Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.

    Rejoignez-nous sur : Serveur de fichiers [NAS] Le Tableau de bord projets Le groupe de travail ICMO

  6. #6
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Tout d'abord, merci beaucoup pour ta réponse, il m'a fallut un peu de temps pour la "digérer". Tant mieux.

    Citation Envoyé par Mac LAK Voir le message
    Non, pas du tout au contraire. J'utilise des librairies d'abstraction à ce niveau, comme ACE ou POCO, qui fournissent une interface commune quel que soit l'OS. Voilà la page des sémaphores pour POCO, par exemple.
    J'y ai jeté un coup d'oeil, c'est excellent. J'en avais juste entendu parlé, mais jamais utilisé. J'ai quelques frissons à introduire de nouvelles lib dans le projet (Qt, Vigra, Xeumeuleu, Boost, ...). Celà dit, la qualité étant au rendez-vous, ça ne posera pas non plus trop de problèmes.

    Citation Envoyé par Mac LAK
    En fait, je définis dans une structure toutes les données nécessaires au thread pour pouvoir bosser (sémaphores, flags, buffers, instances de classes, etc.), sans recopies bien entendu, et c'est cette structure que je passe au thread en paramètre. Chaque thread a besoin de deux sémaphores : un qui lui est spécifique, et un global pour la distribution du boulot.
    A l'initialisation, le thread-père va initialiser la structure, lancer le thread et attendre le sémaphore propre du thread.
    Le thread, lui, va si nécessaire copier la structure (qui peut être sur la pile du thread-père, donc "volatile") dans ses propres variables locales, finir son initialisation, signaler son propre sémaphore (=> libération du père), et ensuite attendre le sémaphore global (= du boulot vient d'arriver).
    OK, je vois l'esprit. Je connaissais pas du tout, alors je prétend pas tout saisir. C'est peut être un peu "compliqué" pour ce que j'essaye de faire, mais je le note dans un coin de ma tête.


    Citation Envoyé par Mac LAK
    Le gros problème est de savoir si le résultat des threads doit être "ordonné" ou non : en effet, tu ne peux pas savoir à l'avance quel thread aura terminé en premier.
    Donc, si les paquets "source" doivent être traités de façon à ce que les paquets "résultat" aient le même ordre, il faut parfois utiliser un étage de plus à la sortie des threads pour rétablir l'ordre.
    Pour ma part, j'utilise le plus souvent un numéro séquentiel unique généré lors de la récupération d'un paquet "source", plus une FIFO unitaire de sortie pour chaque thread. Ensuite, un sérialiseur connecte toutes les FIFO des threads, attends "le" bon numéro, et sérialise dans le bon ordre vers la FIFO finale.
    Dans mon cas, l'ordre n'est pas important. Les traitements sont indépendants. De complexité équivalente, c'est tout. Après la fin des traitements, je dois pouvoir accéder aux résultats de tous les traitements, mais ça, ça ne pose pas problèmes a priori.

    Citation Envoyé par Mac LAK
    Je sais qu'en général, pour ce genre de choses, je prévois des systèmes très optimisés pour garantir les performances. Les FIFO, par exemple, sont gérées sans mutex, j'use et abuse des fonctions atomiques (type InterlockedExchange, InterlockedIncrement, etc.), et je ne fais bien entendu passer que des pointeurs. Je rends les données indissociables entre elles par des ajouts systématiques de structures d'encapsulation, afin de ne toujours avoir besoin que d'un seul pointeur à manipuler.
    En général, si tu ne mets pas de mutex inutiles, tes performances ne sont conditionnées que par peu d'éléments :
    • Vitesse de commutation de contexte après signalement du sémaphore.
      Bien entendu, les OS temps réel s'en sortent mieux que les OS généralistes à ce niveau. Toutefois, Windows (kernel NT) et Linux (kernel 2.6) offrent en général des performances correctes.
    • Gestion réelle des threads : dépend de l'OS, bien sûr, et aussi du nombre de cœurs disponibles.
      En général, je dimensionne mon pool de threads par rapport au nombre de cœurs, de façon à ne pas avoir de threads "inutiles" tout en ayant une utilisation maximale du CPU.


    Côté objets, un "vrai" pool de threads n'est pas forcément nécessaire (voire souhaitable...) dans un tel contexte : contrairement à l'usage possible sur un serveur, par exemple HTTP ou BDD, il n'y a pas de "pics" de charge à assurer, ni de fluctuations très importantes dans la répartition de la charge. Soit tes threads bossent, soit ils ne foutent rien, mais tu n'as pas de grosses contraintes de répartition à faire. Du coup, un simple tableau d'objets-threads suffit en général amplement.

    L'autre point critique, ce sont les FIFO : il est préférable de bien travailler leurs performances, et de tenter au maximum d'éviter l'utilisation de mutex. Souvent, on peut se passer de mutex sur une des extrémités de la FIFO (soit côté application, soit côté threads de travail) : c'est toujours bon à prendre, et de préférence côté threads de travail.
    Toutefois, cela demande pas mal de boulot de faire ça, et les FIFO "toutes prêtes" sont rarement idéales.
    C'est vrai qu'un simple tableau d'objets-threads suffirait dans mon cas. Ma question initiale était : comment les garder actifs et en attente avant de leur donner du boulot à nouveau. Du coup, ThreadPool me convient bien a priori puisque elle implémente déjà ce pool, autant s'en servir.
    Une fois que j'aurai terminé de construire mon système en entier, je verrai si je suis dans les timings ou pas. Et donc si des optimisations seront nécessaires !

    Merci Mac Lak pour ces réponses pointues.

  7. #7
    Inactif  
    Avatar de Mac LAK
    Profil pro
    Inscrit en
    Octobre 2004
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Âge : 49
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Octobre 2004
    Messages : 3 893
    Points : 4 846
    Points
    4 846
    Par défaut
    Citation Envoyé par poukill Voir le message
    Tout d'abord, merci beaucoup pour ta réponse, il m'a fallut un peu de temps pour la "digérer". Tant mieux.
    T'as demandé un spécialiste des pavés ? Me v'là !

    Comment ça, tu l'avais pas demandé ? Mais heuuuu !!!!

    Citation Envoyé par poukill Voir le message
    J'y ai jeté un coup d'oeil, c'est excellent. J'en avais juste entendu parlé, mais jamais utilisé. J'ai quelques frissons à introduire de nouvelles lib dans le projet (Qt, Vigra, Xeumeuleu, Boost, ...). Celà dit, la qualité étant au rendez-vous, ça ne posera pas non plus trop de problèmes.
    Rien ne t'interdit d'introduire une telle librairie sur quelques modules uniquement, sans "refaire" les modules existants (pour l'instant tout du moins) à ce format.
    Quitte à choisir entre ACE et POCO, je te conseille POCO, bien mieux foutue que ACE et beaucoup plus simple à prendre en main.
    Côté trucs intéressants à mettre derrière l'oreille, la librairie ICE (type Corba, mais mieux fait et multi-langages) vaut le détour, si jamais tu as besoin de telles structures réseau dans un projet.

    Citation Envoyé par poukill Voir le message
    OK, je vois l'esprit. Je connaissais pas du tout, alors je prétend pas tout saisir. C'est peut être un peu "compliqué" pour ce que j'essaye de faire, mais je le note dans un coin de ma tête.
    Au pire, tu refais un message et/ou tu postes à la suite de celui-ci si tu as besoin d'éclaircissements. Le but étant surtout de te faire comprendre le principe général : je n'ai sciemment donné aucun exemple de code, et je suis resté "général" de façon à ce que tu puisses, justement, calquer ce principe général sur les éléments que tu connais déjà.

    Citation Envoyé par poukill Voir le message
    Dans mon cas, l'ordre n'est pas important. Les traitements sont indépendants. De complexité équivalente, c'est tout. Après la fin des traitements, je dois pouvoir accéder aux résultats de tous les traitements, mais ça, ça ne pose pas problèmes a priori.
    Alors, FIFO simple en sortie effectivement, au pire avec un numéro d'index / référence quelconque au bloc "source" au sein du bloc "calculé", pour retrouver les petits. Mais nul besoin de sérialiser de façon ordonnée en sortie en effet dans ton cas.

    Citation Envoyé par poukill Voir le message
    C'est vrai qu'un simple tableau d'objets-threads suffirait dans mon cas. Ma question initiale était : comment les garder actifs et en attente avant de leur donner du boulot à nouveau. Du coup, ThreadPool me convient bien a priori puisque elle implémente déjà ce pool, autant s'en servir.
    Une fois que j'aurai terminé de construire mon système en entier, je verrai si je suis dans les timings ou pas. Et donc si des optimisations seront nécessaires !
    En fait, il sera possible (plus ou moins) d'optimiser à partir du thread pool si (et seulement si !) l'objet "thread" est bien le même entre un thread "normal" (simple) et un thread mis en pool.
    Toutefois, cela demande pas mal d'adaptation, et l'inconvénient majeur du pool est que tu ne pourras que difficilement te passer de mutex sur les FIFO (entrée comme sortie), de par son comportement généraliste.

    Après, tout dépend bien sûr de la granularité temporelle dont tu as besoin : si tu n'es pas à 10 / 20 ms près (un quantum de temps, en fait), le pool de threads est d'ores et déjà convenable en terme de performances, c'est certain.
    Si 10 à 20 ms représente une durée "longue" pour ton projet, alors il vaut peut-être mieux envisager d'entrée de jeu la version optimisée. Mais ça, c'est la durée du calcul pour chaque thread, ainsi que le volume global de données, qui te diront si ça vaut le coup ou pas de lancer la grosse artillerie.

    Citation Envoyé par poukill Voir le message
    Merci Mac Lak pour ces réponses pointues.
    Mais de rien.
    Mac LAK.
    ___________________________________________________
    Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.

    Sources et composants Delphi sur mon site, L'antre du Lak.
    Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.

    Rejoignez-nous sur : Serveur de fichiers [NAS] Le Tableau de bord projets Le groupe de travail ICMO

  8. #8
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Citation Envoyé par Mac LAK Voir le message
    Après, tout dépend bien sûr de la granularité temporelle dont tu as besoin : si tu n'es pas à 10 / 20 ms près (un quantum de temps, en fait), le pool de threads est d'ores et déjà convenable en terme de performances, c'est certain.
    Si 10 à 20 ms représente une durée "longue" pour ton projet, alors il vaut peut-être mieux envisager d'entrée de jeu la version optimisée. Mais ça, c'est la durée du calcul pour chaque thread, ainsi que le volume global de données, qui te diront si ça vaut le coup ou pas de lancer la grosse artillerie.
    En fait, ma période d'échantillonnage est de 20 ms. Toutes les 20 ms, j'ai un nouveau calcul qui doit se faire ( la partie vraiment algo est embarqué sur FPGA). Si on compte 3-4 ms de calcul sur le FPGA, il m'en reste 16-17 pour faire le reste des calculs et interpréter. (avec 1, 2, ou 3 chaines de traitements ou max à lancer en parallèle).
    Donc il faut pas que je traines trop non plus !

    T'en penses quoi ?

  9. #9
    Inactif  
    Avatar de Mac LAK
    Profil pro
    Inscrit en
    Octobre 2004
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Âge : 49
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Octobre 2004
    Messages : 3 893
    Points : 4 846
    Points
    4 846
    Par défaut
    Citation Envoyé par poukill Voir le message
    T'en penses quoi ?
    Tu as pu tester pour voir combien de temps prenait une unité de calcul, sur le PC prévu pour récupérer les données ?

    Si tu mets moins de 10 ms pour un bloc, en comptant les multiples coeurs (au moins deux, je suppose ?), ça devrait passer avec le pool de threads. Plus de 20 ms, c'est très tendu, l'optimisation sera nécessaire à priori. Entre les deux... Là, c'est un peu le coup de poker.
    Mac LAK.
    ___________________________________________________
    Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.

    Sources et composants Delphi sur mon site, L'antre du Lak.
    Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.

    Rejoignez-nous sur : Serveur de fichiers [NAS] Le Tableau de bord projets Le groupe de travail ICMO

  10. #10
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Citation Envoyé par Mac LAK Voir le message
    Tu as pu tester pour voir combien de temps prenait une unité de calcul, sur le PC prévu pour récupérer les données ?
    Si tu mets moins de 10 ms pour un bloc, en comptant les multiples coeurs (au moins deux, je suppose ?), ça devrait passer avec le pool de threads. Plus de 20 ms, c'est très tendu, l'optimisation sera nécessaire à priori. Entre les deux... Là, c'est un peu le coup de poker.
    Pour l'instant je n'ai qu'une seule chaine de traitement, qui met 10 ms à faire son traitement en entier.
    Le cahier des charges ayant évolué ( ), je dois maintenant en avoir plusieurs en parallèle (2 semble être le dernier mot). La nouvelle chaine de traitement est de même complexité, donc approximativement le même temps de calcul sur un simple core.
    Le but est donc de les faire tourner toutes les deux en parallèle via un pool de thread. J'espère ne pas dépasser les 15 ms avec ces deux traitements communs sur mon Core2Duo.
    Ca te parait plutôt correct, ou coup de poker ?

    Merci beaucoup pour ton aide (précieuse )

  11. #11
    Inactif  
    Avatar de Mac LAK
    Profil pro
    Inscrit en
    Octobre 2004
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Âge : 49
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Octobre 2004
    Messages : 3 893
    Points : 4 846
    Points
    4 846
    Par défaut
    Citation Envoyé par poukill Voir le message
    J'espère ne pas dépasser les 15 ms avec ces deux traitements communs sur mon Core2Duo.
    Avec une répartition manuelle des threads pour les "fixer" de façon permanente sur un coeur donné, ça devrait passer s'il n'y a pas d'encombrement au niveau du bus mémoire, et ça, ça dépend surtout de la taille des données (sources et finales).

    Pour plus de détails, voir cette discussion. En résumé, tu peux être amené à utiliser un thread de plus que le nombre de cœurs disponibles. Rassures-toi, c'est facile à vérifier en fait : tu fais un bench avec autant de threads que de cœurs, puis avec un thread de plus, et tu prends le plus rapide.
    Mac LAK.
    ___________________________________________________
    Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.

    Sources et composants Delphi sur mon site, L'antre du Lak.
    Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.

    Rejoignez-nous sur : Serveur de fichiers [NAS] Le Tableau de bord projets Le groupe de travail ICMO

  12. #12
    Membre chevronné
    Avatar de poukill
    Profil pro
    Inscrit en
    Février 2006
    Messages
    2 155
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 2 155
    Points : 2 107
    Points
    2 107
    Par défaut
    Citation Envoyé par Mac LAK Voir le message
    Avec une répartition manuelle des threads pour les "fixer" de façon permanente sur un coeur donné, ça devrait passer s'il n'y a pas d'encombrement au niveau du bus mémoire, et ça, ça dépend surtout de la taille des données (sources et finales).
    L'utilisation d'un pool de thread fixe t-elle l'exécution d'un thread sur un coeur donné? Si non, bah je vois pas comment faire.
    Taille des données? 500 Ko à peu près par thread. Donc c'est pas trop gros, mais pas super petit non plus. J'ai pas assez d'expérience pour pouvoir quantifier l'encombrement du bus mémoire. Les algos ont pas spécialement été pensé pour limiter cet encombrement. D'ailleurs je vois pas trop comment le faire, si tu pouvais m'éclairer un petit peu sur le sujet. J'accède déjà à tout par pointeurs (intelligents vu que je suis en C++ ), sans aucunes recopies.

    Citation Envoyé par Mac LAK
    Pour plus de détails, voir cette discussion. En résumé, tu peux être amené à utiliser un thread de plus que le nombre de cœurs disponibles. Rassures-toi, c'est facile à vérifier en fait : tu fais un bench avec autant de threads que de cœurs, puis avec un thread de plus, et tu prends le plus rapide.
    Super discussion, j'ai tout noté !
    Je ferai un petit bench, ce sera rapide de voir quelle est la meilleure solution !

    Merci à toi pour toutes ces précisions !

  13. #13
    Inactif  
    Avatar de Mac LAK
    Profil pro
    Inscrit en
    Octobre 2004
    Messages
    3 893
    Détails du profil
    Informations personnelles :
    Âge : 49
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Octobre 2004
    Messages : 3 893
    Points : 4 846
    Points
    4 846
    Par défaut
    Citation Envoyé par poukill Voir le message
    L'utilisation d'un pool de thread fixe t-elle l'exécution d'un thread sur un coeur donné? Si non, bah je vois pas comment faire.
    Oh, ça, c'est facile en fait, voir affinité processeur. Au pire, même si le pool ne gère pas l'affinité, tu pourras la gérer manuellement dans la phase d'initialisation de chaque thread (tu passes le masque dans les données du thread, tout simplement).

    Sur Windows, tu fixes ça via SetThreadAffinityMask, j'avais mis un bout de code Delphi pour gérer ça (très facile à traduire en C++ : ignorer Win32Check/RaiseLastOSError, FillChar=memset, Shr/Shr = décalages binaires, le reste est trivial).
    Sous Linux, c'est sched_setaffinity() qu'il faut utiliser, mais je te conseille plutôt l'utilisation d'une librairie dédiée (l'API de réglage d'affinité peut varier fortement d'une version à l'autre de Linux) : hwloc semble être un bon truc, c'est l'évolution de la librairie Portable Linux Processor Affinity (PLPA).

    Citation Envoyé par poukill Voir le message
    Taille des données? 500 Ko à peu près par thread. Donc c'est pas trop gros, mais pas super petit non plus.
    Trop gros pour le cache, à priori, donc effectivement tu risques d'avoir intérêt à utiliser un thread de plus pour occuper le temps CPU au maximum.

    Citation Envoyé par poukill Voir le message
    J'ai pas assez d'expérience pour pouvoir quantifier l'encombrement du bus mémoire.
    Oh, c'est super simple en fait : est-ce que tout tient dans le cache du CPU, ou pas ?
    Si oui, aucun souci, le bus n'est quasiment jamais encombré. Si non, tu peux avoir deux unités d'exécution (threads ou processus) qui désirent aller chercher des données en même temps, et là, l'un des deux sera bloqué en attendant que l'autre aie terminé. D'où l'intérêt d'avoir un thread de plus qui arrivera à s'intercaler plus ou moins entre les deux threads pour occuper le rab de temps CPU. Ce fameux thread "supplémentaire" gagne, en général, à ne pas être contraint sur un cœur en particulier.

    Citation Envoyé par poukill Voir le message
    Les algos ont pas spécialement été pensé pour limiter cet encombrement. D'ailleurs je vois pas trop comment le faire, si tu pouvais m'éclairer un petit peu sur le sujet. J'accède déjà à tout par pointeurs (intelligents vu que je suis en C++ ), sans aucunes recopies.
    Il faut déjà des blocs de données contigus, bien sûr, donc si tu veux rester dans l'esprit STL, c'est le std::vector impérativement et de façon non-négociable. Bien entendu, le vecteur ne doit pas contenir des pointeurs vers des données, mais bien les données elles-même !
    Ensuite, côté fragmentation des données, ce n'est hélas pas toujours possible.

    Exemple : si tu as 500 ko de données, et que tu veux calculer une FFT dessus, tu vas avoir besoin des données de ce bloc de façon "aléatoire", et non pas de façon séquentielle. Donc, fragmenter le bloc n'est pas possible sans repenser l'algo général de A à Z pour diminuer la période du signal.
    Au contraire, si tu as 500 ko de données qui seront parcourues de façon parfaitement séquentielle, sans notion de "contexte", alors tu peux fragmenter à ta guise sans problème.

    Tout le problème se résume à :
    • Puis-je lire les données de façon séquentielle, ou ai-je besoin d'un accès aléatoire dans le bloc ?
    • Ai-je besoin pour traiter l'élément N du contexte (=données précédentes) ?
    Si tu as besoin d'un accès aléatoire ou du contexte, la fragmentation des unités de calcul peut être soit très difficile, soit carrément impossible.

    Citation Envoyé par poukill Voir le message
    Super discussion, j'ai tout noté !
    Je ferai un petit bench, ce sera rapide de voir quelle est la meilleure solution !
    Merci à toi pour toutes ces précisions !
    De rien ! J'aime bien ce genre de questions aussi !
    Mac LAK.
    ___________________________________________________
    Ne prenez pas la vie trop au sérieux, de toutes façons, vous n'en sortirez pas vivant.

    Sources et composants Delphi sur mon site, L'antre du Lak.
    Pas de question technique par MP : posez-la dans un nouveau sujet, sur le forum adéquat.

    Rejoignez-nous sur : Serveur de fichiers [NAS] Le Tableau de bord projets Le groupe de travail ICMO

Discussions similaires

  1. Réponses: 6
    Dernier message: 12/10/2010, 17h13
  2. [Thread] Travail en parallèle et attente
    Par Jabbal'H dans le forum Concurrence et multi-thread
    Réponses: 5
    Dernier message: 13/10/2008, 16h28
  3. [Threads] Afficher un gif durant un travail
    Par genki dans le forum Général Dotnet
    Réponses: 3
    Dernier message: 20/06/2007, 10h04
  4. Un thread de travail qui se termine et se libère tout seul
    Par bigquick dans le forum Threads & Processus
    Réponses: 15
    Dernier message: 24/06/2005, 13h58
  5. Réponses: 18
    Dernier message: 06/04/2005, 14h09

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