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.
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 #define CHANS 2 #define COUNT 10 #define LOOPS 666
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
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;
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.
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(); } }
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�.
Sch�ma illustrant la synchronisationEh 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�.
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(); } }
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 !
Partager