Comptons les processeurs,Y'a pas de thread chez nous,Y'en a chez la voisine... |
Besoin primaire
Sur des machines disposant de plusieurs processeurs, il arrive que l'on ait besoin pour (tenter d') assurer qu'un processus ait de bonnes performances, que l'on "épingle" le processus sur un processeur: en clair, on indique au système qu'il doit impérativement exécuter le dit processus sur le processeur de numéro N.Si le processus épinglé a plusieurs threads logicielles, ou si l'on a plusieurs processus, on peut être amené à forcer l'exécution de ces différentes threads (ou processus) sur différents processeurs (numéros N, N+1, N+2...).
Facile donc ?
Posix nous fournit tout ce qu'il faut ? Euh non. Quand on a ce genre de besoins, il faut utiliser les outils spécifiques du système. Portabilité ? Ben, non. De toute manière, si vous faîtes ça pour des raisons d'amélioration de performances, votre travail est à faire (ou pour le moins à adapter) pour chaque système d'accueil.Fixer un (des) processeur(s) d'exécution sur Linux
- taskset est une commande qui permet de contraindre l'exécution d'un programme ou d'un processus sur un ou des processeurs spécifiés en paramètre.
- Par programmation en C, on peut utiliser sched_affinity ou pthread_setaffinity_np.
Facile donc
Oui, il suffit d'utiliser les outils ci-dessus et le tour est joué. :-)Ça a pourtant l'air facile. On fixe des numéros de processeurs, en vérifiant que l'on nomme des processeurs existants de la machine (pour simplifier : de numéro inférieur au nombre de processeurs de la machine, que l'on peut obtenir sur Linux par la commande nproc).
En fait, et bien évidemment, c'est souvent, sinon le début des ennuis, tout au moins l'entrée dans un espace de complexité accrue. Tant pis pour vous.
Un processeur = un processeur ?
Ce n'est malheureusement pas une question de philo. Ce n'est pas vraiment qu'il y ait des processeurs plus z'égaux que d'autres. Mais il faut quand même être très attentif. Je sais, tu sais, il sait, nous savons,... qu'il existe des processeurs multi-cœurs et des processeurs hyper-threadés. Pour simplifier, ne considérons que le monde x86 (32 et/ou 64 bits)."Hiérarchie de processeurs"
On a donc des:- processeurs (puces avec plein de pattes qui s'enfichent dans des "sockets" sur la carte mère, et que du coup on appelle assez souvent sockets) qui contiennent
- des cœurs (cores) que l'on peut parfois appeler abusivement processeurs qui eux-mêmes contiennent
- des hyper-threads que l'on appelle souvent et abusivement des processeurs.
- les hyper-threads d'un même cœur partagent souvent les mêmes unités de calculs et les caches mémoires L1, L2, L3. Comme elles se partagent les unités de calcul, la puissance de calcul de ces deux hyper-threads, n'est (très généralement) pas le double de celle d'un processeur sans hyper-thread. (On reviendra sur cet aspect dans un article ultérieur).
- Les cœurs d'une même puce peuvent partager des caches L3 et L2.
- Des puces différentes ne partagent rien. Quoique....
Problèmes de choix
Du coup,voilà quelques problèmes :- Si on met 2 processus (pthreads) sur deux cpu (hyper-threads) du même cœur, ils vont se partager (ou être en compétition pour) l'utilisation du cache L1. Mais surtout la puissance de calcul n'est pas forcément celle espérée.
- Si on met 2 processus (pthreads) sur deux cpu sur des cœurs différents, en supposant qu'il n'y ait pas d'autres processus squattant les hyper-threads co-localisées sur ces cœurs, la puissance de calcul "disponible" devrait être meilleure. Mais les partages de caches étant différents, qui sait...
De la numérotation à la topologie
Naïvement, on pourrait penser que si le cpu N°0 correspond à la première hyper-thread du premier cœur de la première puce, le cpu N°1 serait logiquement la deuxième hyper-thread du premier cœur de la première puce...Perdu!
Comment savoir alors ? Grâce à la commande lscpu (voir la page manuel). Voici ce qu'elle indique sur une machine :
fa$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 24 On-line CPU(s) list: 0-23 Thread(s) per core: 2 Core(s) per socket: 6 CPU socket(s): 2 NUMA node(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 45 Stepping: 7 CPU MHz: 1901.000 BogoMIPS: 3799.47 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 15360K NUMA node0 CPU(s): 0,2,4,6,8,10,12,14,16,18,20,22 NUMA node1 CPU(s): 1,3,5,7,9,11,13,15,17,19,21,23 fa$ |
- 2 sockets (puces)
- qui sont équipées chacune de 6 cœurs
- qui sont chacun "hyper-threadé".
En utilisant l'argument -p, on obtient un résultat difficilement lisible mais beaucoup plus instructif:
fa$ lscpu -p # The following is the parsable format, which can be fed to other # programs. Each different item in every column has an unique ID # starting from zero. # CPU,Core,Socket,Node,,L1d,L1i,L2,L3 0,0,0,0,,0,0,0,0 1,1,1,1,,1,1,1,1 2,2,0,0,,2,2,2,0 3,3,1,1,,3,3,3,1 4,4,0,0,,4,4,4,0 5,5,1,1,,5,5,5,1 6,6,0,0,,6,6,6,0 7,7,1,1,,7,7,7,1 8,8,0,0,,8,8,8,0 9,9,1,1,,9,9,9,1 10,10,0,0,,10,10,10,0 11,11,1,1,,11,11,11,1 12,0,0,0,,0,0,0,0 13,1,1,1,,1,1,1,1 14,2,0,0,,2,2,2,0 15,3,1,1,,3,3,3,1 16,4,0,0,,4,4,4,0 17,5,1,1,,5,5,5,1 18,6,0,0,,6,6,6,0 19,7,1,1,,7,7,7,1 20,8,0,0,,8,8,8,0 21,9,1,1,,9,9,9,1 22,10,0,0,,10,10,10,0 23,11,1,1,,11,11,11,1 fa$ |
On voit ici que ce sont les cpu N°0 et N°12 qui sont "co-localisés" sur le même cœur de la même socket, et qu'ils partagent les mêmes caches L1, L2, L3...
Voilà!
"Petits compléments"
Si l'envie vous prenait de plonger plus avant dans les problèmes de topologie des processeurs et caches, prenez la peine de lire :-
- Tudor David, Rachid Guerraoui, Vasileios Trigonakis
- SOSP 2013
- Contrôle de la bande passante mémoire dans les systèmes à criticité mixte par sous-réservation.
- Antoine Blin, Julien Sopena, Gilles Muller et Youssef Laarouchi
- COMPAS 2014
- Ne le cherchez pas tout de suite. Le texte n'est pas encore disponible.
- Portable Hardware Locality (hwloc)
- Découvert ce jour!
Aucun commentaire:
Enregistrer un commentaire