Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

MultiThread Windows

2 réponses
Avatar
brice.allenbrand
Bonjour,

J'essaie de trouver l'avantage =E0 utiliser des threads. Pour cel=E0 j'ai
fait un programme qui chronom=E8tre l'execution d'une fonction d'une
mani=E8re classique puis =E0 l'aide de threads (de 1 =E0 N) Je m'attendais
pour N=3D2 (ayant un Dual Core) =E0 avoir un gain de temps. Pas =E9norme
mais tout de m=EAme sensible. Ce n'est absolument pas le cas. Pour N=3D2
le programme met 2 fois plus de temps et d'une mani=E8re g=E9n=E9rale pour
N, il met N fois plus de temps.

Voici la fonction :

DWORD WINAPI ThreadFunc(LPVOID lpParam )
{
unsigned int i,j;

for(i=3D0;i<=3D2000000000;i++)
j+=3Di;
return 0;
}

Je me suis donc dit bigre ! peut =EAtre que je monopolise les registres
et les acc=E8s m=E9moires pour chaque fois la m=EAme chose. Le Dual Core est
un faux biprocesseur. J'ai donc chronom=E8tr=E9 l'execution s=E9quentielle
puis paral=E8lle de deux fonctions tr=E8s diff=E9rentes.

DWORD WINAPI ThreadFunc1(LPVOID lpParam )
{
unsigned int i;
char *buff;

for(i=3D0;i<=3D200000;i++)
{
buff=3Dcalloc(1000000,4);
free(buff);
}
return 0;
}

DWORD WINAPI ThreadFunc2(LPVOID lpParam )
{
unsigned int i,j;

for(i=3D0;i<=3D2000000000;i++)
j+=3Di;
return 0;
}

L'une compte, l'autre alloue de la m=E9moire. Effectivement les
r=E9sultats sont prometteurs, le Dual Core s'en sort bien. Ma question
est donc la suivante : j'ai un programme qui contient une zone
critique appel=E9e 1 million de fois. Elle est compos=E9e d'une allocation
et de tests m=E9moires brutaux. Elle est pure dans le sens o=F9 les
variables ont les m=EAmes valeurs en sortie qu'en entr=E9e. Elle n'utilise
pas de variable globale. Quel est l'int=E9r=EAt d'en faire un thread et
l'appeler 2 fois si elle va monopoliser les m=EAmes ressources
syst=E8mes ? Par exemple comment parall=E9liser une fonction r=E9cursive ?

Merci :)
Et pi de la doc serait aussi la bienvenue, je suis newbie dans le
domaine Windows/Createthread et tutti quanti. Ce que je trouve est
soit trop simple, soit trop th=E9orique. Rien de concr=EAt.

--------------------------------------------
(pour info : le code source avec une seule fonction, puis la version
avec deux fonctions)

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

DWORD WINAPI ThreadFunc(LPVOID lpParam )
{
unsigned int i,j;

for(i=3D0;i<=3D2000000000;i++)
j+=3Di;
return 0;
}

int main(char argc,char **argv)
{
int NB;
DWORD dwThreadId,dwThrdParam=3D1,pWait;
HANDLE hThread[NB];
int i;
clock_t endclock,startclock;

if(argc!=3D2)
exit(0);
NB=3Datoi(argv[1]);

startclock=3Dendclock=3Dclock();
ThreadFunc(NULL);
endclock=3Dclock();
printf("%f\n",(float)(endclock-startclock)/(float)CLOCKS_PER_SEC);
for(i=3D0;i<NB;i++)
hThread[i]=3DNULL;
for(i=3D0;i<NB;i++)
if(!(hThread[i]=3DCreateThread(NULL,
10,ThreadFunc,NULL,CREATE_SUSPENDED,&dwThreadId)))
break;
printf("%d threads\n",i);
startclock=3Dendclock=3Dclock();

for(i=3D0;i<NB;i++)
SetThreadPriority(hThread[i],15);
for(i=3D0;i<NB;i++)
ResumeThread(hThread[i]);
SetThreadPriority(GetCurrentThread(),-15);
for(i=3D0;i<NB;i++)
if(hThread[i])
{
pWait=3DWaitForSingleObject(hThread[i],INFINITE);
if(pWait=3D=3DWAIT_OBJECT_0)
CloseHandle(hThread[i]);
}
endclock=3Dclock();
printf("%f\n",(float)(endclock-startclock)/(float)
(i*CLOCKS_PER_SEC));
}
------------------------------------


#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

DWORD WINAPI ThreadFunc1(LPVOID lpParam )
{
unsigned int i;
char *buff;

for(i=3D0;i<=3D200000;i++)
{
buff=3Dcalloc(1000000,4);
free(buff);
}
return 0;
}

DWORD WINAPI ThreadFunc2(LPVOID lpParam )
{
unsigned int i,j;

for(i=3D0;i<=3D2000000000;i++)
j+=3Di;
return 0;
}

int main(char argc,char **argv)
{
int NB;

if(argc!=3D2)
exit(0);
NB=3Datoi(argv[1])<<1;

DWORD dwThreadId,dwThrdParam=3D1,pWait;
HANDLE hThread[NB];
int i;
clock_t endclock,startclock;

startclock=3Dclock();
ThreadFunc1(NULL);
ThreadFunc2(NULL);
endclock=3Dclock();
printf("%f\n",(float)(endclock-startclock)/(float)CLOCKS_PER_SEC);
for(i=3D0;i<NB;i++)
hThread[i]=3DNULL;
for(i=3D0;i<NB;)
{
if(!(hThread[i++]=3DCreateThread(NULL,
10,ThreadFunc1,NULL,CREATE_SUSPENDED,&dwThreadId)))
break;
if(!(hThread[i++]=3DCreateThread(NULL,
10,ThreadFunc2,NULL,CREATE_SUSPENDED,&dwThreadId)))
break;
}
printf("%d threads\n",i);
startclock=3Dclock();
for(i=3D0;i<NB;i++)
SetThreadPriority(hThread[i],15);
for(i=3D0;i<NB;i++)
printf("pri:%d\n",GetThreadPriority(hThread[i]));
for(i=3D0;i<NB;i++)
ResumeThread(hThread[i]);
SetThreadPriority(GetCurrentThread(),-15);
pWait=3DWaitForMultipleObjects(i,hThread,TRUE,INFINITE);
endclock=3Dclock();
for(i=3D0;i<NB;i++)
if(hThread[i])
{
if(pWait=3D=3DWAIT_OBJECT_0)
CloseHandle(hThread[i]);
}
printf("%f\n",(float)(endclock-startclock)/(float)
(i*CLOCKS_PER_SEC));
}

2 réponses

Avatar
Charlie Gordon
"Harpo" a écrit dans le message de news:
466d74f1$0$5108$
On Mon, 11 Jun 2007 07:46:41 -0700, brice.allenbrand wrote:

Bonjour,

J'essaie de trouver l'avantage à utiliser des threads. Pour celà j'ai
fait un programme qui chronomètre l'execution d'une fonction d'une
manière classique puis à l'aide de threads (de 1 à N) Je m'attendais
pour N=2 (ayant un Dual Core) à avoir un gain de temps. Pas énorme
mais tout de même sensible. Ce n'est absolument pas le cas. Pour N=2
le programme met 2 fois plus de temps et d'une manière générale pour
N, il met N fois plus de temps.


C'est déjà pas mal.
Le programme n'utilisant que du CPU, sur un monoprocesseur lui faire faire
2 fois plus de choses prend 2 fois plus de temps plus l'overhead du au
multithreading.
Pour simplifier, il n'y a un intérêt que si
- les threads font des IOs pendant lesquels le CPU peut être utilisé
pour un autre thread
- il y a plusieurs processeurs. (Je ne sais pas ce que c'est le Dual
Core).


google, wikipedia ?

C'est un chip avec 2 processeurs dedans qui se comporte comme un
bi-processeur moyennant le support convenable du système d'exploitation.

Quant à aller deux fois plus vite avec 2 processeurs, cela dépend de ce que
font les threads : s'ils utilisent une ressource partagée, il y a fort à
parier qu'ils attendront à tour de rôle de pouvoir y accéder, grâce à un
sémaphore ou autre mutex, et donc l'amélioration de performance sera faible,
voire négative. De même, si les threads font un usage débridé de RAM, c'est
la bande passante de celle-ci qui sera le facteur limitant : avoir diminué
la localité des accès en permettant à 2 processeurs d'en faire en parallèle
pourrait même causer un ralentissement supplémentaire (on peut comparer cet
effet à ce qui se passe quand on lance 2 antivirus en même temps ;-).

D'une façon générale, il faut énormément d'humilité pour aborder la
programmation en multi-thread.
Les questions de concurrence sont *difficiles* et mal comprises par plus de
99% des programmeurs, en C/C++ comme en java.
La plupart des problèmes peuvent se résoudre sans cela, même sous Windows,
au besoin avec plusieurs process ou des constructions plus simples.
L'humilité est également de mise sur le terrain de l'optimisation : le seul
juge de paix est le chronomètre, et encore faut-il que les tests soient
faits avec des données réalistes. Les résultats sont souvent surprenants,
et toujours provisoires : l'évolution du hardware les rend obsolètes
rapidement, parfois avant même la mise en production. Alors utiliser des
threads pour optimiser la performance a toutes les chances de mener à de
mauvaises solutions.

Chqrlie.

PS: l'adjectif "difficile" pourrait être mal compris : disons que en
comparaison, réécrire printf avec le support de toutes les options est un
problème facile, qui d'ailleurs ne requiert pas de multi-threading, juste
une bonne dose de courage. Lui rajouter ne serait-ce que le support du
multi-threading, malheureusement nécéssaire à cause de cette mode stupide,
lui fait perdre en efficacité jusqu'à un facteur entier.


Avatar
Antoine Leca
En news:,
brice allenbrand écrivit:
J'essaie de trouver l'avantage à utiliser des threads.


C'est une construction qui permet d'utiliser de manière efficace les
ressources avec de grandes latences, comme par exemple les sockets, et plus
généralement les éléments d'E/S.


Pour celà j'ai
fait un programme qui chronomètre l'execution d'une fonction d'une
manière classique puis à l'aide de threads (de 1 à N)


Est-ce qu'il y a bien N travaux indépendants à faire ? parce que si non, tu
va être obligé de gérer la synchronisation entre les tâches/threads, et cela
risque de coûter plus de temps que ce que cela ne peut rapporter.

Je m'attendais pour N=2 (ayant un Dual Core) à avoir un gain de temps.


Cela suppose que le processeur est la ressource critique, et que le temps de
mise en place des threads et de changements de contexte est négligeable
devant celui d'exécution.



Pas énorme mais tout de même sensible. Ce n'est absolument pas le cas.
Pour N=2 le programme met 2 fois plus de temps et d'une manière
générale pour N, il met N fois plus de temps.


Mmm.
Résultats sur une babasse mono processeur (P4 2.4GHz), sans optimisation:
boucle seule 5s, 1 thread 5s, 2 threads 5s/thr, 10 threads 5s/thr. Si on met
des clock()+printf() dans la fonction, on voit que tous les threads sont
exécutés séquentiellement.

Résultats sur une babasse HT (P4 2.8GHz, chargée), sans optimisation: boucle
seule 5,2s, 1 thread 5,15s, 2 threads 4,4s/thr, 10 threads 4,6s/thr. Si on
met des clock()+printf() dans la fonction, on voit que N threads sont
exécutés en parallèle.

Je ne sais pas quel est le quantum de temps, mais à mon avis il est faible
(quelques dizaines de ms), donc il doit y avoir un nombre important de
changements de contexte.


Voici la fonction :

DWORD WINAPI ThreadFunc(LPVOID lpParam )
{
unsigned int i,j;

for(i=0;i< 00000000;i++)
j+=i;
return 0;
}


Cette fonctrion, avec un compilateur optimisateur raisonnable, devrait être
remplacée par

DWORD WINAPI ThreadFunc(LPVOID lpParam ) {
return 0;
}

Maintenant, si tu lances cette fonction sur N threads, tu va mesurer N fois
le temps de création d'un thread. Et ce temps est, grosso modo,
proportionnel à N.
(et le fait qu'il y ait plusieurs processeurs, sans effet visible, peut
probablement être attribué au fait que la création de thread est une tâche
affectée exclusivement au processeur maître)


Ma question
est donc la suivante : j'ai un programme qui contient une zone
critique appelée 1 million de fois. Elle est composée d'une allocation
et de tests mémoires brutaux. Elle est pure dans le sens où les
variables ont les mêmes valeurs en sortie qu'en entrée. Elle n'utilise
pas de variable globale. Quel est l'intérêt d'en faire un thread et
l'appeler 2 fois si elle va monopoliser les mêmes ressources
systèmes ?


Si ta fonction alloue sur la même mémoire (donc l'allocation va nécessiter
une gestion de la synchronisation, et à la limite cela fonctionnera mieux en
faisant l'allocation en dehors du code multiprogrammé), tu peux avoir un
gain à utiliser un multiprocesseur pour faire tes « tests mémoire brutaux »
si le dit test consomme plus de CPU que de bande passante d'accès à la
mémoire.
Sinon, c'est de toute manière l'accès mémoire qui va limiter, et le fait que
ce soit multi ou monoprogrammé, avec des threads, des processus ou que
sais-je, ou encore le choix du compilo, du langage voire du capitaine ne
changeront pas grand chose. Par contre l'architecture machine (NUMA ou
pas,par exemple) risque d'avoir beaucoup d'importance.


Par exemple comment paralléliser une fonction récursive ?


Je ne sais pas, mais je ne crois pas que ce soit une question sans réponse,
il devrait y avoir de la littérature là-dessus (genre Knuth, au hasard).

Sinon, pour _une_ fonction récursive donnée, il est peut-être possible de
dériver un algorithme (peut-être non récursif, ou en utilisant des
coroutines) équivalent, qui sera plus efficace en environnement
multiprogrammé.


Antoine