MultiThread Windows

Le
brice.allenbrand
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.

Voici la fonction :

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

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

Je me suis donc dit bigre ! peut être que je monopolise les registres
et les accès mémoires pour chaque fois la même chose. Le Dual Core est
un faux biprocesseur. J'ai donc chronomètré l'execution séquentielle
puis paralèlle de deux fonctions très différentes.

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

for(i=0;i<=200000;i++)
{
buff=calloc(1000000,4);
free(buff);
}
return 0;
}

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

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

L'une compte, l'autre alloue de la mémoire. Effectivement les
résultats 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é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 ? Par exemple comment paralléliser une fonction récursive ?

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éorique. Rien de concrêt.

--
(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=0;i<=2000000000;i++)
j+=i;
return 0;
}

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

if(argc!=2)
exit(0);
NB=atoi(argv[1]);

startclock=endclock=clock();
ThreadFunc(NULL);
endclock=clock();
printf("%f",(float)(endclock-startclock)/(float)CLOCKS_PER_SEC);
for(i=0;i<NB;i++)
hThread[i]=NULL;
for(i=0;i<NB;i++)
if(!(hThread[i]=CreateThread(NULL,
10,ThreadFunc,NULL,CREATE_SUSPENDED,&dwThreadId)))
break;
printf("%d threads",i);
startclock=endclock=clock();

for(i=0;i<NB;i++)
SetThreadPriority(hThread[i],15);
for(i=0;i<NB;i++)
ResumeThread(hThread[i]);
SetThreadPriority(GetCurrentThread(),-15);
for(i=0;i<NB;i++)
if(hThread[i])
{
pWait=WaitForSingleObject(hThread[i],INFINITE);
if(pWait==WAIT_OBJECT_0)
CloseHandle(hThread[i]);
}
endclock=clock();
printf("%f",(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=0;i<=200000;i++)
{
buff=calloc(1000000,4);
free(buff);
}
return 0;
}

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

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

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

if(argc!=2)
exit(0);
NB=atoi(argv[1])<<1;

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

startclock=clock();
ThreadFunc1(NULL);
ThreadFunc2(NULL);
endclock=clock();
printf("%f",(float)(endclock-startclock)/(float)CLOCKS_PER_SEC);
for(i=0;i<NB;i++)
hThread[i]=NULL;
for(i=0;i<NB;)
{
if(!(hThread[i++]=CreateThread(NULL,
10,ThreadFunc1,NULL,CREATE_SUSPENDED,&dwThreadId)))
break;
if(!(hThread[i++]=CreateThread(NULL,
10,ThreadFunc2,NULL,CREATE_SUSPENDED,&dwThreadId)))
break;
}
printf("%d threads",i);
startclock=clock();
for(i=0;i<NB;i++)
SetThreadPriority(hThread[i],15);
for(i=0;i<NB;i++)
printf("pri:%d",GetThreadPriority(hThread[i]));
for(i=0;i<NB;i++)
ResumeThread(hThread[i]);
SetThreadPriority(GetCurrentThread(),-15);
pWait=WaitForMultipleObjects(i,hThread,TRUE,INFINITE);
endclock=clock();
for(i=0;i<NB;i++)
if(hThread[i])
{
if(pWait==WAIT_OBJECT_0)
CloseHandle(hThread[i]);
}
printf("%f",(float)(endclock-startclock)/(float)
(i*CLOCKS_PER_SEC));
}
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Charlie Gordon
Le #992361
"Harpo" 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.


Antoine Leca
Le #992358
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

Publicité
Poster une réponse
Anonyme