Le Multi-Processeur avec DelphiConfiguration d'une application SMP sous Delphi. I. Introduction II. Les affinitées II-A. Application a Delphi III. L'HyperThreading III-A. Le Load Imbalance III-B. Gestion des Load Imbalance I. Introduction
Pour les applications serveur acceptant plusieurs connexions clientes, il est souvent
nécéssaire de multi-threader les processus appellés afin de ne pas avoir a gérer
un sheduler ou un queue de connexion.
La mise en pratique des applications
multi-threadées n'est pas compliquée outre-mesure et permet dans le cas d'une
serveur Multi-Processeur d'améliorer les pérformances de l'application, a condition
de pouvoir gérer tout ça correctement.
Une des principale amélioration est
d'authorisée l'utilisation de tout/certains processeurs du serveur, voir de certains processeurs logiques
si l'hyperthreading est utilisable en fonction du type de thread.
II. Les affinitées
On nomme affinitée processeur un masque binaire qui permet de connaitre le comportement
d'une application et de ses threads vis-a-vis des processeurs de la machine.
Supposons un serveur possédant 4 processeurs (CPU1, CPU2, CPU3, CPU4)
: Dans le cas ou une application créée 60 threads, comment vont se répartir les
threads de l'application (chaque thread s'éxecutant séparement) ?
Et bien tout est question d'affinitée processeur. L'affinitée de l'application ou
d'un processus se controle avec l'appel a la fonction SetProcessAffinityMask contenue
dans kernel32.dll. Etudions d'un peu plus près cette fonction.
Cette fonction permet d'affecter un affinityMask pour ses différents threads.
Comme dis précédement, un AffinityMask est un masque binaire et il s'utilise de
la manière suivante:
Les processeur on une valeur en puissance de 2:
Pour l'exemple suivant, nous souhaitons travailler avec les processeurs CPU1
et CPU3, les autrse étant réservés par une autre application.
La valeur du masque sera la somme des valeurs de CPU :
Masque = CPU1(1)+CPU3(4) = 5;
La valeur de notre masque sera donc 5; De la même manière pour travailler
avec seulement le processeur 4, le masque vaudra 8. Une fois cette valeur affectée,
tout les threads créés utiliserons les processeurs disponibles. Mais de quelle
manière seront ils répartis entre les processeurs si on demandre l'utilisation de
plusieurs CPU ? Et bien c'est le kernel qui va décider de la répartition de la charge
en fonction de la charge de chaque processeur. On appelle ça du LoadBalancing.
Il est toutefois possible d'affecter manuellement une affinitée à un thread a condition
que celle-ci soit en accords avec l'affinitée de l'application principale. Par exemple, il n'est pas possible de demander l'affectation d'un thread au processeur
CPU4 si seulement CPU1 et CPU3 sont définis lors de l'appel à SetProcessAffinityMask.
Dans ce cas la fonction retournera False et une appel à GetLastError
permettra de retrouver le problème.
L'affectation d'un thread a un AffinityMask se fait a travers l'appel a SetThreadAffinityMask. Il s'appelle de la même manière que la fonction précédente
à la différence pret que c'est le Handle du thread qui doit être passé en paramètres.
II-A. Application a Delphi
III. L'HyperThreading
L'HyperThreading est une technologie qui permet une utilisation maximale des ressources
systemes en créant des processeurs logiques qui partagent ces ressources.
Vous l'avez surement déja vue ou utilisée, il sagit d'une simulation d'un Bi-Processeur avec
un seul CPU possédant 2 Cores.
Windows détecte donc 2 processeurs au démarage alors d'un seul est présent dans la machine (en réallité,
il y a 1 seul processeur avec 2 noyeaux).
On pourrais donc penser que les applications utilisées sur un processeur HyperThread seront plus
rapides, le systeme étant capable de paralleliser les threads sur 2 CPU.
Si l'application n'est pas programmées otpimalement, il n'en est rien...
III-A. Le Load Imbalance
On appelle Load Imabalance le fait que la charge des travail ne soit
pas répartie correctement entre les processeurs physiques et logiques aun moment donné.
Par exemple, un processeur physique voit ses 2 processeurs logiques surchargés alors que l'autre processeurs reste en IDLE.
Le systeme d'exploitation ne verra pas la différence étant donné que pour l'OS, l'Hyperthreading
correspond a 2 processeurs distincts alors qu'en réalitée, c'est un et un seul CPU qui travaille.
Ce cas ne peux apparaitre que si il y a moins de Threads actifs que de processeurs logiques.
Prenons un exemple:
Une machine possède 2 processeurs physiques HyperThread (donc 4 processeurs en tout dont 2 logiques).
Un serveur d'application créé 3 threads au lancement qui doivent calculer un chiffre d'affaire depuis une base de données.
2 threads vont s'executer sur un processeurs physique et 1 autre thread sur l'autre processeur physique.
Le thread qui fonctionne seul sur 1 processeur a toutes les chances d'être plus performant car il n'a pas a partager
les ressources d'un autre processeur (cache...). Nous somme donc un cas de Load Imbalance. Les performances
des 2 threads seront dégradées par rapport a l'autre CPU du fait qu'elle devront partager les ressources du processeur pendant l'execution.
Dans le cas ci-dessus, il n'y a pas de configuration parfaite. Le Load Imbalance peut arriver
a n'importe quel moment du fait que le Kernel de l'OS peut faire migrer des threads d'un CPU a l'autre
en fonction de la charge.
Dans le cas de 2 processeurs distincts, le changement de CPU d'un thread a une
très forte impacte sur les ressources systèmes du fait que les informations du cache ne sont pas les mêmes entre les processeurs.
Dans le cas de l'HyperThreading, les ressources étant partagées entre les threads, le changement de processeur n'a qu'une impacte
minime sur les performances.
III-B. Gestion des Load Imbalance
Afin de gérer au mieux ces cas critiques (non, non, ce n'est pas l'OS qui vous rendra ce service...), il convient
de programmer l'application de telle sorte qu'en fonction du type de Thread, l'AffinityMask de ce dernier sera
géré par le programme.
Prenons un programme qui possède 4 threads (notre programme de calcul de chiffre d'affaire).
2 threads (Thread A et Thread B) font les calculs de moyenne (Opération en float) et 2 autres threads (Thread C et Thread D)
suppriment des enregistrements de la base de données (opération en entiers).
Afin d'optimiser au mieux l'application, il faut penser a éviter le partage des ressources CPU pour les threads
qui font des calculs lourds (virgule flottante) et ne pas hésiter a séparer les threads qui ne font que des accès mémoires.
Par exemple :
Permet que l'unitée de gestion en virgule flottante de chaque CPU soir dédiée a un thread pour cette application.
On optimisera ainsi le partage des ressources processeurs et les performances de l'application.
Pour avoir plus d'informations, je ne peux que vous conseiller d'aller jeter un oeil sur le site
d' Intel afin de récupérer les informations sur la technologie HyperThreading.
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
|