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 :

Problème de lenteur du multi-threading


Sujet :

Threads & Processus C++

Mode arborescent

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné
    Avatar de ABD-Z
    Homme Profil pro
    Ingé. webapps embarquées – Admin/mainteneur serveur/BDD – Formateur WordPress – Desiger : logo/site
    Inscrit en
    Septembre 2016
    Messages
    302
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 28
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Ingé. webapps embarquées – Admin/mainteneur serveur/BDD – Formateur WordPress – Desiger : logo/site

    Informations forums :
    Inscription : Septembre 2016
    Messages : 302
    Billets dans le blog
    3
    Par défaut Problème de lenteur du multi-threading
    Bonjour à vous tous!
    Je me retourne vers vous, les pros, pour avoir vos opinions sur le multi-threading, et plus précisément en C++.

    Je souhaite en fait améliorer les performances de mon séquenceur de musique temps réel (voir branche performance) en apportant justement du multi-treading.

    Pour faire simple, le séquenceur peut être composé de N canaux.
    Chaque canal, à un instant T, calcul son propre échantillon audio. Les résultats de chaque canal vont être à la fin additionnés pour former l’échantillon audio de la musique à l’instant T.
    On additionne N échantillons pour former un échantillon audio effectif qui sera sauvegarder dans le tampon audio.

    Cette opération de calcul des échantillons au niveau des canaux se fait séquentiellement : c’est-à-dire qu’on commence par calculer l’échantillon du canal N-1, puis celui du canal N-2 jusqu’au canal 0.
    (oui t’as capté, commencer la boucle par N-1 c’est plus rapide!)

    Je me suis dit : «Tiens, ça serait pas mal si l’on parallélisait tout ça !».
    Surtout que les canaux n’ont pas d’interdépendances ; chaque canal a ses propres données et donc pas de prise de tête avec la concurrence, de ressources bien entendu.

    Du coup, avant de me lancer dans la modification de mon projet, j’ai fait un croquis pour simuler l’exécution en séquentielle et en «parallèle» (oui je me des guillemets parce qu’on sait tous que les threads c’est pas forcément parallèle en réalité) et ainsi comparer leur temps d’exécution.

    Dans le fichier de simulation (disponible, j’ai déclaré trois constantes en #define.
    CHANSc’est le nombre de canaux que l’on va créer.
    COUNTle nombre de fois que l’on va répéter le «calcul d’échantillonnage»
    LOOPSest utilisé dans les boucles for pour faire écouler du temps et ainsi simuler le temps de «calcul d’échantillonnage» au niveau du canal.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    #define CHANS 2
    #define COUNT 10
    #define LOOPS 666
    Premièrement, on a la classe Seq_Chan qui sera utilisé pour simuler le calcul séquentiel.
    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
    class Seq_Chan {
    private:
    	unsigned char id = 0;
    	static unsigned char chancount;
    public:
    	Seq_Chan() {
            this->id = Seq_Chan::chancount++;
        }
     
        void play()
        {
        	//std::cout << "Seq Chan " << +this->id <<" play  " << std::endl;
        	for(int i = 0; i<LOOPS; ++i);
        	//std::cout << "Seq Chan " << +this->id <<" finish play  " << std::endl;
        }
    };
    unsigned char Seq_Chan::chancount = 0;
    Dans le main rien de plus simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    Seq_Chan seq_chan[CHANS];
    	for(unsigned int j = 0; j < COUNT; ++j){
    		//std::cout << "*******************************"<<j<<"*******************************"<<std::endl;
    		for(char i=(CHANS-1); i >=0; --i){
    			seq_chan[i].play();
    		}
     
    	}
    Deuxièmement, concernant la classe qui gère les threads, Chan,elle stocke le std::thread (processor) et une structure Mutexcontenant deux std::mutex (maint2thread, thread2main) verrouillées à partir du constructeur. L’important ici c’est de pouvoir synchroniser avec le thread principal C’est pour ça que deux mutex sont nécessaires : une pour donner le départ, et une pour avertir de la fin.
    La classe dispose donc d’une méthode pour accéder à la structure contenant les mutex.
    Dans la méthode play, on verrouille main2threadpour bien synchroniser.
    Il faudra déverrouiller main2threaddans le main afin d’exécuter la suite de la méthode play(première boucle for dans le main).
    La deuxième boucle for dans le main verrouille thread2mainpour synchroniser le main avec les threads. Dans la méthode play, avant de reboucler, thread2mainest déverrouillé.
    Nom : threadingsynch.png
Affichages : 346
Taille : 21,3 Ko
    Schéma illustrant la synchronisation
    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    struct Mutex{
        std::mutex maint2thread, thread2main;
    };
     
    class Chan {
    private:
    	std::thread processor;
    	Mutex mut;
    	unsigned char id = 0;
    	static unsigned char chancount;
    	bool stopped = false;
    public:
    	Chan() {
    		std::thread p(&Chan::play, this);
            this->processor = std::move(p);
            mut.maint2thread.lock();
            mut.thread2main.lock();
            this->id = Chan::chancount++;
        }
     
        void play()
        {
        	while(true){
        		this->mut.maint2thread.lock();
        		if(this->stopped){
        			break;
        		}
        	    //std::cout << "Chan " <<  +this->id <<" play thread " << std::endl;
        	    for(int i = 0; i<LOOPS; ++i);
        	    //std::cout << "Chan " <<  +this->id <<" finish play thread " << std::endl;
     
        		this->mut.thread2main.unlock();
        	}
     
        }
     
        void wait(){
        	std::cout << "wait processor " <<  +this->id << " for finish" << std::endl;
        	this->processor.join();
        }
     
        void stop(){
        	std::cout << "stop processor " <<  +this->id << std::endl;
        	this->stopped = true;
        	this->mut.maint2thread.unlock();
        }
     
        Mutex* getMut(){
        	return &this->mut;
        }
     
    };
     
    int main(){
    	Chan chan[CHANS];
    	for(unsigned int j = 0; j < COUNT; ++j){
    		//std::cout << "*******************************"<<j<<"*******************************"<<std::endl;
    		for(short i=(CHANS-1); i >=0; --i){
    			chan[i].getMut()->maint2thread.unlock();
    		}
     
    		for(short i=(CHANS-1); i >=0; --i){
    			chan[i].getMut()->thread2main.lock();	
    		}
    		//std::cout<<"----add samples-----"<< std::endl;
    	}
     
    	for(short i=(CHANS-1); i >=0; --i){
    		chan[i].stop();	
    	}
     
    	for(short i=(CHANS-1); i >=0; --i){
    		chan[i].wait();	
    	}
    }
    Eh bien, en chronométrant le temps écoulé pour le calcul séquentiel et pour le calcul «parallèle», je me suis rendu compte que l’approche multi-threadé est intéressante lorsque la fonction play est vraiment coûteuse en temps. Or, en réalité, mon séquenceur calcule l’échantillon d’un canal en 900 ns jusqu’à à peu près 20000 ns (à cause des traitements d’effets sûrement). Et j’ai testé en mettant LOOPS à 66 (900 ns sur ma machine) et à 6666 (18000 ns) voire même plus et les résultats me montrent que l’approche threadé n’est pas du tout rentable pour mon cas! Du moins, vu comment je l’ai implémenté.
    Pourtant en théorie, ça doit être beaucoup plus performant qu’en séquentielle.
    Je met le doigt sur les boucles for dans le main gérant les mutex et sur la performance des threads (changement de pile, contexte etc... c’est pas du vrai parallélisme).

    Ma question : déjà, est-ce que j’ai bien implémenté l’approche threadé ? Sachant que la synchronisation est obligatoire pour mon cas ou sinon j’aurais des threads qui iront plus vite que la musqiue !

    Deuxième question : comment vraiment paralléliser ? Et surtout efficacement. Le processeur de ma machine est quadricœur, ça devrait donc le faire !
    Fichiers attachés Fichiers attachés

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

Discussions similaires

  1. Problème de lenteur d'un thread
    Par augur67700 dans le forum Threads & Processus
    Réponses: 10
    Dernier message: 31/05/2013, 23h52
  2. Probléme serveur multi-thread
    Par hebus44 dans le forum Entrée/Sortie
    Réponses: 2
    Dernier message: 14/11/2007, 22h32
  3. Problème Seveur multi-thread
    Par Doom2Darkness dans le forum C++
    Réponses: 14
    Dernier message: 05/06/2007, 19h32
  4. problme de multi thread
    Par L4BiN dans le forum Concurrence et multi-thread
    Réponses: 22
    Dernier message: 25/04/2007, 16h47
  5. Réponses: 11
    Dernier message: 14/02/2006, 00h26

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