Intro
Cet article est motivé par ma psychose du process inutile et d'avoir le moins de trucs possible qui tourne sur mon ordi, particulièrement celui "de jeu", qui est le plus puissant.
Ce qui n'a absolument aucun sens à moins d'être dans ma tête ou de partager ma bizarritude. Bizarrerie. Etrangeté.
Quand je vois ces gens qui ont une zone de notification BOURRéE de trUCS ça me fait le même effet que de voir un navigateur avec un cordon visiblement infini d'onglets, ma tension artérielle monte, j'ai mal mon coeur pour ce petit processeur qui a rien demandé.

Pourtant, les processeurs (surtout les modernes de maintenant sur un OS moderne de maintenant), ça les dérange pas trop de gérer tout un tas de trucs à la fois. Tant que ça reste raisonnable. En général.
Alors, est-ce que ça vaut le coup d'être un extrémiste du processus superflu?
Etat des lieux
Le gestionnaire des tâches de Windows vous dit combien de processus (entendre, un programme) et threads (entendre, un morceau indépendant du programme - sous-programme?) sont en cours d'exécution.
Habituellement chez moi c'est dans ces eaux là:
Il y a des gens ils ont plus ou moins mais en général c'est plus et ça a tendance à augmenter avec l'âge de la machine ce qui participe, selon moi, à rendre les installations les plus fraiches de Windows plus performantes.
Sur un Linux "Desktop" (le mien) les processus sont en fait plus nombreux, mais il y a beaucoup moins de threads en contrepartie. C'est sans doute lié à l'usage courant d'instancier plusieurs fois le même processus en parallèle sur les systèmes de type Unix.
Je compte à peu près 290 processus et 354 threads sur ma Manjaro.
Pour info et histoire d'apprendre encore un truc en plus, les "Handles" mentionnés par le gestionnaire de tâches représentent à la fois tous les descripteurs de process et threads, mais aussi tous les descripteurs de fichiers, sockets etc. ouverts, y compris, je pense, un descripteur par "fenêtre" (visible ou pas) active. On est quand même sur un OS qui s'appelle "Windows", hein.
En pratique ce nombre ne nous intéresse pas, mais vous pouvez le voir augmenter de 10000 après avoir lancé une appli React ouverte simultanément dans VSCode, parce qu'il faut au moins ouvrir 10000 fichiers pour afficher "YO LE mOnDE" dans un navigateur.

La question est plutôt pourquoi je parle de JavaScript dans TOUS mes articles????? mqlsdkfjqsmlkdfjds
L'expérience
Je me mets à écrire, à l'arrache, un bidule en ligne de commande qui soit capable de créer une quantité déterminée de threads qui FONT RIEN.
J'ai pensé à C/C++ pour manipuler directement l'API Win32 mais en fait Rust fait la même chose, je crois. Oui.
Simuler une situation avec tout plein de threads "réaliste" c'est un peu trop complexe à mon goût. J'étais juste un peu curieux de voir quelle genre de charge est occasionnée par de nombreux changements de contexte.
Keke c'est un changement de contexte?
Je me rends compte que ce serait quand même utile d'introduire deux ou trois concepts de systèmes d'exploitation comme ça en plus, vous aurez au moins appris quelque chose dans cet article parce que peut-être la conclusion sera un tantinet décevante.
Je suis obligé de remonter jusqu'à ce bon vieux Microprocesseur. C'est une puce électronique capable de comprendre et gérer un certain nombre "d'instructions" du genre:
- Mets-moi le chiffre 42 dans la mémoire à tel emplacement — c'est une des instructions les plus utilisées — Certains emplacements mémoire correspondant à des comportements définis par le système d'exploitation comme la sortie console par ex.;
- Ajoute 1 à cet emplacement mémoire;
- Si tel emplacement mémoire vaut 43, saute à tel endroit du programme;
- Bouge le contenu de tel emplacement mémoire dans un registre ou un autre emplacement mémoire;
- Effectue un OU EXCLUSIF entre ces deux emplacements mémoire;
- Et tout ce genre de choses.
On voit que la "mémoire" revient souvent, le processeur est étroitement couplé à la mémoire vive qui ne sert à rien sans processeur, mais le processeur ne sert à rien sans mémoire vive. Essayez de démarrer un ordi après avoir retiré les barrettes de mémoire pour voir.
Je n'ai pas non plus parlé de la PILE (stack) qui possède ses propres instructions mais est en réalité dans la mémoire vive aussi (si on mets de côté tous les mécanismes de cache des processeurs).
Un programme est juste une suite d'instructions qu'un processeur est capable de comprendre, qu'il lit de haut en bas, une à une (mis à part certaines instructions spéciales et les instructions de saut).
Chaque programme possède sa "pile" et l'utilise, notamment, pour appeler des procédures et leur passer des paramètres. Parce que oui, les processeurs proposent aussi des instructions pour déclarer et appeler des procédures.
Un processeur peut exécuter un seul programme à la fois. Pourtant, ça fait depuis la presque-préhistoire de l'informatique que certains systèmes d'exploitation, comme UNIX (et pas comme MSDOS qui n'est justement pas multitâche) sont capables de multitâche, c'est à dire d'exécuter plusieurs programmes à la fois sur un seul microprocesseur.
Sauf qu'il s'agit d'une illusion. C'est le système d'exploitation qui retire et replace les différents programmes en cours sur le processeur, il n'y a qu'un seul programme qui tourne à un moment donné.
Ces changements — qui sont les fameux changements de contexte à l'origine de toute cette discussion — sont extrèmement rapides et peuvent même profiter d'un programme qui doit de toutes façons attendre des données d'un truc lent (disque dur, réseau, ...) pour donner la main à un autre programme qui attendait.
Comme tout s'enchaîne très vite, on a l'impression que plusieurs programmes s'exécutent en même temps.
Quelque part après les années 2000 (~2006) c'est devenu courant pour les gens comme vous et moi d'avoir des microprocesseurs équipés de plusieurs coeurs qui partagent la même mémoire.
C'est un cas un peu particulier de "multi-processeurs" qui est de nos jours devenu majoritaire et permet réellement à plusieurs processus de tourner en même temps.
En réalité il y a en a un autre qui existe depuis plus longtemps et permet de diviser un seul processeur en deux "coeurs logiques". Ce qui s'appelle l'Hyperthreading chez Intel ou SMT chez AMD et qui essaye de rattraper un handicap des processeurs dits x86 dont l'étape de décodage d'instruction est très longue (à l'échelle microprocesseur, à notre échelle c'est ultra rapide) parce que ces processeurs reconnaissent un grand nombre d'instructions différentes.
On s'égare un tout petit peu mais ça pourra servir pour plus tard puisque mes plateformes de test supportent l'hyeprthreading/SMT.
Dans tous cas, même si vous avez toujours moins de processus que de processeurs en même temps (en comptant aussi le système d'exploitation qui compte pour plusieurs processus (oui c'est aussi un programme en fait)), des changements de contexte vont tout de même se produire, ne fut-ce que pour passer la main au système d'exploitation car il s'agit d'un type particulier de changement de contexte que je vais devoir me retenir de développer parce que cette section est déjà trop longue.
L'organe qui gère ces commutations de contexte fait d'ailleurs partie du système d'exploitation et est appelé "ordonnanceur" (process scheduler en anglais).
L'ordonnanceur tient compte de tout un tas de trucs pour essayer de jongler au mieux les différents processus, sortir ceux qui attendent quelque chose qui va prendre un bout de temps, sortir ceux QUI ABUSENT (utilisent beaucoup trop de processeur alors que d'autres processus attendent du temps processeur) et essayer d'avoir un semblant de justice et d'équité dans la distribution des ressources système.
Il y a aussi des états de processus ou threads qui veulent, en gros, dire "je suis en train de pieuter je passe mon tour" et l'ordonnanceur les ignore totalement. Je pensais qu'ils étaient tout de même programmés pour exécution de temps en temps mais j'ai découvert dans le cadre de cet article que non... Tant qu'un process dort sous certains états bien déterminés, il n'utilise aucun temps processeur.
Il y a un truc qu'on ne peut pas changer un peu comme le premier principe de la thermodynamique: les changements de contexte ne sont pas gratuits, et le travail de l'ordonnanceur non plus.
D'ailleurs, au plus l'ordonnanceur est complexe, au plus on risque de ressentir son impact s'il doit intervenir très souvent.
Celui de Windows est typiquement plus complexe de celui de Linux (qui historiquement en proposait d'ailleurs plusieurs ordonnanceurs, dont un extrèmement simple — mais compiler manuellement était nécessaire pour choisir et donc tout le monde s'en fout) et je pense que celui de Windows 11 est le plus complexe jusqu'à présent d'après ce que je peux lire en surface — Je n'ai pas réalisé d'expériences sous Windows 11 pour cet article.
Restons tout de même réalistes: l'ordonnanceur reste très rapide, parcourir une liste de miliers d'éléments et empiler/dépiler certains trucs c'est pas un soucis pour un processeur moderne. Pourtant, que ce passe-t-il s'il s'agit réellement de permuter des miliers de fois par seconde? Est-ce qu'on peut mesurer un impact notable?
Dans quel état qu'on va mettre ces threads?
L'ordonnanceur a accès à une liste de tous les processus et threads avec leur état actuel.
Dans l'univers le plus basique de la base de pas se prendre la tête, il y aurait deux états:
- En cours d'exécution (running);
- En attente (ready).
Quelques autres états existent et tant Windows que Linux (et MacOS aussi j'imagine) implémentent un mécanisme de "priorité" et d'affinité processeur (si votre ordinateur dispose de plusieurs processeurs).
Pour Windows, nous verrons plus loin qu'il existe plusieurs types d'état Waiting, où le processus n'est pas en attente d'exécution sur le microprocesseur mais plutôt volontairement arrêté dans l'attente d'un événement particulier (qui en général, est lent à l'échelle processeur (pratiquement tout est lent à cet échelle, je précise)).
Par exemple, le thread attend d'être réveillé par un autre thread ou processus ou il attend la fin d'une opération sur un volume de stockage.
Attendre que l'utilisateur tape quelque chose — dans Notepad ou Word par exemple — constitue de même l'un de ces statuts d'attente. La plupart des threads présents sur un Windows en cours de fonctionnement sont dans l'un de ces statuts et, en pratique, ne sont pas du tout programmés sur le processeur tant qu'ils restent dans cet état.
Je découvrirai plus tard qu'ouvrir 15000 processus qui restent en attente n'a en fait pratiquement aucun effet sur les performances (ça utilise pas mal de mémoire par contre). Je n'ai pas testé si c'est valide pour les processus "graphiques" (comme Notepad, qui ouvre une fenêtre et attend que l'on tape quelque chose dedans).
Une des complexité de l'ordonnanceur Windows est la considération particulière des applications "graphiques", c'est à dire qui ont une FENETRE puisque ça s'appelle Windows quand même hein. Genre ce serait pas mal que le thread de notepad se réveille rapidement quand on clique dans la fenêtre ou commence à taper même si le processeur est à 100% ou on va avoir une bonne vieille impression de manque total de réactivité.
Un moment (Windows Vista) ils ont aussi décidé d'ajouter des conditions liées au "multimedia" où la lecture de vidéo ou audio en utilisant les API de Windows reçoit une attention particulière de sorte à rester réactive, ceci comprennant les jeux vidéos et applications audio professionnelle qui étaient plutôt réputées pour poser problème sous Windows à moins d'utiliser des drivers spéciaux (et encore, pas question de trop charger le processeur).
Encore un autre exemple dont j'ai déjà parlé: l'ordonnanceur de Windows 11 ajoute d'autres règles particulières notamment liée à la séparation des processus avant ou arrière-plan (cette distinction n'existe pas sous Linux alors qu'elle est présente depuis très longtemps sur Windows et Windows Server) et tout ceci rend l'ordonnanceur Windows relativement complexe.
Ce qui nous amène à une liste d'états de threads assez longue que je comprends pas trop complètement surtout si on le compare à la simplicité de ce qui est utilisé sous Linux — dont le code (majoritairement du C) est téléchargeable en un joli paquet cadeau ouvert donc c'est facile de jeter un coup d'oeil:
* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
/* RT specific auxilliary flag to mark RT lock waiters */
#define TASK_RTLOCK_WAIT 0x1000
#define TASK_STATE_MAX 0x2000
Extrait de include/linux/sched.h pour le noyau 5.19.
C'est juste une vieille bande de symboles #define dont les principaux sont RUNNING (que Linux considère identique à l'état "READY" qui n'existe pas de manière indépendante sous Linux), INTERRUPTIBLE, UNINTERRUPTIBLE (qui sont deux état d'attente).
L'ordonnaceur de Linux a beau être de facto plus simple, le fichier qui contient la majeure partie de son code (kernel/sched/fair.c) titre à 310Kb, ce qui est assez balaise comme quantité de texte dans un code source sujet à des standards de qualité élevés.
Créons des threads!
Je vois comment mettre facilement un thread en attente avec Rust mais j'ignore à quel état exact cela correspond du point de vue de l'ordonnanceur Windows puisque tous ces langages bas-niveau modernes peuvent compiler vers plusieurs systèmes d'exploitations et plateformes totalement différentes. On va devoir essayer qu'est-ce que ça donne...
J'ai écrit un bidule vite fait, que j'ai mis sur Github, dans l'idée de tester quelques scénarios et j'en ai isolé trois en particulier:
- Threads qui annoncent s'endormir pour une certaine durée;
- Threads qui attendent pour toujours (rien ne les réveille);
- Processes qui attendent pour toujours.
C'est possible de voir l'état des threads (et processes) dans un programme bien utile nommé Process Explorer qui se télécharge à un endroit complètement bizarre chez Microsoft.
Le programme est un peu étrange aussi, c'est comme si quelqu'un avait créé le vrai gestionnaire des tâches mais on l'a envoyé paître parce que le truc était trop moche et velu et que déjà les gens captent rien au gestionnaire des tâches en mode "détails", on va peut-être pas leur expliquer qu'un vaccin à ARN messager n'a aucune chance de modifier leur génôme ou que les fraises sont des polyakênes et pas des fruits.
Un double click sur un process ouvre une fenêtre de détails qui-fait-peur avec un onglet "Threads".

Parmi les éléments intéressants:
- On voit l'état, ici Wait:DelayExecution qui est l'état d'un thread qui est "endormi" pour un temps donné (au minimum, l'ordonnanceur a le droit d'attendre plus longtemps si nécessaire) au niveau du code; Cet état de thread est en fait extrêmement rare et je comprends maintenant pourquoi — peut-être j'explique plus tard sinon spa une grande perte;
- Start Time, utile pour mettre les autres chiffres en perspective;
- Context Switches: Hey je vous avais dit que ça allait être utile d'avoir introduit ce concept au chaptire précédent! Non? Je l'avais pas dit? OK ben voilà, le nombre de basculements de contexte est indiqué.
Avoir un nombre élevé de basculement de contexte pour un temps donné n'est pas nécessairement une indication d'utilisation processeur de l'extrême. Disons que ça indique tout de même une propension à demander régulièrement des ressources.
Les chiffres Kernel time et User time devraient être une meilleure indication des ressources consommées, surtout le premier des deux qui sous entend une priorité élevée pour l'ordonnanceur (c'est du temps processeur en mode "administrateur" si vous voulez), mais en général c'est un chiffre un peu insignifiant donc je nie.
Ces chiffres deviennent intéressant si le processus tourne depuis longtemps. C'est même pas très clair ce qu'ils veulent exactement dire, personne n'a l'air au courant sur Stackoverflow alors pensez bien que moi non plus.
D'après la doc de l'outil, le chiffre Cycles serait vraiment le nombre de cycles processeur que le thread a utilisé. Je n'ai aucune idée de si c'est vraiment indicatif parce que j'imagine qu'on est rapidement dans les milliards et ce compteur a forcément une limite. A vous de voir.
Histoire d'ajouter un peu de piment j'ai fait en sorte que mon "thread qui s'endort" il vérifie si une variable est toujours à une certaine valeur (elle y est parce que je la change jamais) avant de s'endormir. Cette valeur est partagée entre les threads sous forme de Booléen "atomique" pour ceux que ça intéresse (lol).
Si je compte les changements de contexte par rapport à la durée de fonctionnement, ça correspond exactement à un changement de contexte par intervalle... D'endormissement... Que j'ai défini.
Par ex. si les threads s'endorment toutes les demi-secondes, et que le programme tourne depuis 1 minute, il y a eu exactement 120 changement de contexte par threads. Il semblerait que l'ordonnanceur soit très précis, même avec 15000 threads qui demandent tous à se réveiller toutes les 0.5s, ce qui deviendra ma durée de test standard PAR PUR CHOIX ARBITRAIRE QUE c'est parce qu'il fallait en faire un.
L'état dans lequel ils se mettent est Wait:DelayExecution, et comme dit plus haut, force est de constater que pratiquement personne n'utilise sleep.
Effectivement à moins de vouloir temporiser d'une manière assez inéfficace ou créer un genre de tâche planifiée foireuse, c'est mieux d'attendre jusqu'à un certain événement utilisateur, sémaphore, message, verrou etc. Plutôt que de se réveiller d'office après X temps, peut-être pour rien.
Les autres tests que j'ai prévu mettent le thread ou processus en attente et il n'est jamais ordonnancé. Ces expériences n'ont pratiquement aucun impact CPU, même avec 15000 threads ou processus.
Ils se mettent en état Wait:WrAlertByThread:

Scénario de test
C'est très difficile de simuler une grande quantité de threads réels parce que certains ne font sans doute pas grand chose alors que d'autres se réveillent souvent, accèdent au réseau, écrivent des fichiers quelque part, lisent et hachent tout un tas de fichier (oui je te regarde Windows DEFENDEUUre!), ...
Mes trucs qui se réveillent toutes les demi-secondes pour pratiquement-rien-faire ne ressemblent à rien qui existe pour de vrai. Mais c'est pas grave, j'ai tout de même espoir que ça représente en grande partie la charge que crée l'ordonnanceur.
Je vais donc créer trois cas: 1500, 5000 et 15000 threads. Ce qui représente x3 à chaque fois. Plus ou moins. Mais tout est "plus ou moins" dans cette aventure qu'est la vie.
Réveil toutes les 0.5s pour juste check une variable et c'est tout.
Je pense que c'est pas mal de le dire tout de suite: 15000 threads (ou 15000 processus) n'est pas du tout un scénario réaliste, 1500 est le seul scénario à avoir une fesse dans la réalité en terme de nombre, mais comme mes threads ne font rien, nos fesses sont déjà à une distance très respectable de la réalité précitée et tout ce paragraphe ne sert absolument à rien. Comme mon chat.
En pratique j'utilise deux benchmarks:
Je limite le bench 7zip à 4 threads pour représenter vaguement (très très vaguement) ce qu'un jeu moderne pourrait utiliser et puis de toutes manières la plupart de mes processeurs de test ont 4 coeurs.
Le bench s'invoque comme ceci en ligne de commande:
7z b -mmt4
Il s'agit parfois de changer "7z" par "7zip.exe" et tout ça mais bon voilà quoi.
Les résultats ressemblent à ceci:
7-Zip 21.07 (x64) : Copyright (c) 1999-2021 Igor Pavlov : 2021-12-26
mt4
Windows 10.0 19043
x64 17.7100 cpus:24 128T f:1FB10F2774C
AMD Ryzen 9 3900X 12-Core Processor
1T CPU Freq (MHz): 4304 4305 4412 4584 4600 4614 4604
4T CPU Freq (MHz): 404% 4490 390% 4525
RAM size: 32677 MB, # CPU hardware threads: 24
RAM usage: 889 MB, # Benchmark threads: 4
Compressing | Decompressing
Dict Speed Usage R/U Rating | Speed Usage R/U Rating
KiB/s % MIPS MIPS | KiB/s % MIPS MIPS
22: 30150 325 9035 29330 | 399585 398 8568 34090
23: 30048 333 9203 30616 | 397422 397 8657 34388
24: 30007 356 9055 32264 | 393665 397 8704 34547
25: 29684 367 9227 33893 | 382167 397 8559 34013
---------------------------------- | ------------------------------
Avr: 29972 345 9130 31526 | 393210 397 8622 34259
Tot: 371 8876 32893
Où appraissent les millions d'opérations par seconde (MIPS) pour la compression et la décompression. J'utilise le chiffre "Tot" qui représente la moyenne des moyennes (oui) de compression et décompression.
Pour les systèmes de test, j'ai trouvé trois machines à l'arrache dont voici les processeurs:
- Intel i7 930 (2.8 Ghz base) — 4c/8t
- Intel Xeon E5620 (2.4 Ghz base) — 4c/8t
- AMD Ryzen 9 3900X (3.8 Ghz base) — 12c/24t
Ces plateformes sont toutes totalement différentes (les types de mémoire n'ont rien à voir, entre autres choses).
Aucune n'est fixée en fréquence, l'hyperthreading est actif et certaines mesures n'ont pas été prises le même jour. C'est un pitit peu le zourboude. Le BAZAAR comme on dit en Bruxellois. Une bonne vieille moyenne sur plusieurs mesures devrait m'aider à lisser tout ça et de toutes manières, vu les écarts-type que je me choppe (oui je parle en termes de statistiques de sa maman, ça vous épate???? OUI???), ça a l'air OK.
Résultats (faut le dire vite)
Voyons ce qu'il en est-il des résultats de base, avec la charge de threads et process totalement arbitraire qui était présente sur la machine à ce moment là.
Je peux juste dire que c'est sous les 180 processus et 2500 threads et j'utilise Windows 10.
Pour essayer d'avoir moins de variations à la mode de Windows, je désactive la protection temps réel de l'antimalware/antivirus et je mets les mises-à-jour en pause pour 7 jours.
Je conseille aussi d'attendre un peu après le démarrage de Windows parce qu'il fais parfois des drôles de trucs comme une tâche planifiée liée à "Windows compatibility telemetry" qui chez moi crée un pic processeur en créant autant de threads que possible... Ce qui réveille tous mes ventilos.

Je pense qu'on peut désactiver ce truc, ce sera un sujet pour une autre fois.
Threads en "sleep", 0,5s d'intervalle
Intel Core i7 930
Pour CPU-Z sur un seul coeur avec 1500 threads on a une différence de 1% par rapport au niveau de base.
Il faut 15000 threads à ma sauce étrange pour perdre à peu près 5% de performances.
En multi-coeur, c'est à dire avec le processeur au maximum, 15000 de mes threads ne font chuter les performances que de 1.2%. Il n'y a pratiquement aucun impact avant ça, le test à 1500 est même légèrement au dessus du niveau de référence, ça traine autour de la marge d'erreur étant donné que l'écart-type est entre 3.8 et 4.8.
L'effet est peu prononcé si le processeur est à 100%, ce que je discuterai plus tard parce que j'ai l'impression qu'on va revoir la même tendance pour les autres processeurs.
Par contre on retrouve pratiquement le même effet que pour le test mono-coeur sur le benchmark de 7zip limité à 4 threads bien que... Mes résultats soient un peu étranges. L'écart type est faible mais le résultat 15000 threads au supérieur au 5000. Peut-être que ma méthodologie est légèrement foireuse ou qu'il y avait une erruption solaire à ce moment là. C'est sûrement la deuxième proposition. On ne saura jamais.
Intel Xeon E5620
Les scores sont relativement similaires à l'i7 930, c'est un autre 4 coeurs / 8 threads de la demi-génération qui suit (oui, Intel fait des demi-générations); il a plus de cache L3 mais sa fréquence de fonctionnement est inférieure.
Par contre, les résutlats mono-coeur CPU-Z de ce processeur semblent indiquer qu'il est davantage affecté par la présence de ma soupe de threads puisqu'on a presque 15% entre la référence et les 15000 threads et on est déjà à 6% avec le test 1500 threads.
En multi-coeur on retrouve à nouveau peu de différences entre le score de référence et celui avec 15000 threads (environ 3%); cette différence est plus faible avec un nombre de threads plus raisonnable, le score 1500 est identique au score de référence si on retire la marge d'erreur.
Vient ensuite le benchmark 7zip sur 4 threads où la différence est assez prononcée mais avec un écart-type un peu sauvage (en rouge sur le graphique qui suit).
On retrouve à nouveau plus ou moins le même effet qu'avec CPU-Z en mono-coeur: 16% de différence entre le niveau de référence et les 15000 threads.
Les résulats sont clairement affectés, mais pas autant que je l'aurais pensé.
AMD Ryzen 9 3900X
Euhmmm.... Ouais on parle d'un 12 coeurs sorti plus de 9 ans après les deux autres "choix" de comparaison pour ce glorieux article.
Forcément ça va être légèrement injuste, j'ai pas une armoire pleine de processeurs à disposition. En tous cas pas de processeurs qui fonctionnent (???) donc on fait avec ce qu'on a.
C'est intéressant de voir quel est l'effet après tant de générations.
Le boost simple-coeur des Ryzen Zen2 est connu pour être très capricieux en cela qu'il s'agit très, très littéralement d'un boost "single-thread" un peu comme quand on commande un café quelque part et qu'on vous file une minuscule tasse et ni sucre ni lait.
Il suffit qu'il y ait quelques autres process qui trainent, et le boost max annoncé par AMD n'est jamais atteint. Ce comportement sera "corrigé" (plutôt dans le sens "le marketing s'est acheté une conscience") à la génération suivante.
Ceci me laisse penser que la différence de 7.5% entre référence et 15000 threads à ma sauce est probablement plus élevée que ce qu'on trouverait à la concurrence ou pour la génération suivante (ou précédente d'ailleurs).
Ceci dit... La différence est très faible pour les 1500 et 5000 threads, il y a probablement un point de débordement quelque part après les 5000 threads où les performances chutent plus rapidement.
Pour le benchmark CPU-Z multi-coeur, on trouve à peu près le même écart que celui du Core i7 930: 1.3% entre les deux extrèmes. Ne parlons même pas du reste où la différence est encore plus faible.
Le benchmark 7zip limité à 4 threads est peu affecté sur ce processeur, seulement 1.1% de différence avec la référence pour le test à 15000 threads.
Les autres processeurs sont des 4 coeurs + 4 coeurs logiques (hyperthreading) et l'hyperthreading d'Intel n'est pas génial (celui d'AMD non plus mais il est quand même mieux), ce qui signifie qu'on a des threads qui se battent pour tourner sur les 4 coeurs, ce qui n'arrive pas sur le processeur 12 coeurs. Enfin, c'est ma théorie. Youpie.
Threads et processus en pause
La plupart des processus et threads sont dans un état d'attente qui n'a rien à voir avec le "sommeil" que j'utilisais pour tous les tests ci-dessus.
Je pensais cependant qu'avoir une grande quantité de threads ou de processus en attente pouvait tout de même affecter les performances parce que peut-être que l'ordonnanceur il place tout de même ces processus et threads de temps en temps sur le processeur juste pour voir s'ils sont toujours en vie.
Et ben non. D'après mes tests, les performances sont inchangées avec tout un tas de threads en attente (que je ne réveille jamais, je re-précise au cas où).
Evidemment, pour un processeur même pas moderne, parcourir une liste de 20000 éléments plutôt que 1500 ne change pratiquement rien (ça change rien à l'échelle de nous), c'est presque instantané dans les deux cas. Je dis ça pour souligner que la taille de la liste que l'ordonnanceur doit manipuler n'a probablement aucun effet.
Du coup ça sert à rien que je vous montre des chiffres.
Il y a plusieurs statuts d'attente, par exemple en Rust:
- Attendre la fin d'un thread depuis un autre (join());
- Attendre sur une condvar;
- Utiliser thread.park();
- Attendre un message sur un canal de communication inter-threads;
- Liste très probablement pas exhaustive.
Le résultat est le même quoi qu'il en soit, ces threads ne sont jamais ordonnancés et donc n'ont aucun impact sur les performances, ils occupent juste de la mémoire.
Quid de créer TOUT UN TAS de processus?
D'après ce que je comprend, l'ordonnanceur Windows ne fait pas la différence entre processus et threads, c'est tout la même chose pour lui.
Normalement, créer 15000 processus qui attendent immédiatement ne devrait pas avoir d'impact sur les performances non plus.
J'utilise un vieux script .bat (plus ou moins glâné de Stackoveflow, évidemment) pour créer 15000 processus:
@echo off
for /l %%x in (1, 1, 15000) do (
echo Starting process %%x
start /B many-threads-experiment.exe 6
)
La création de ces processus est un peu lente et ça fait planter le task manager si on essaye de l'ouvrir (même sur le 3900X):
J'en ai un qui a réussi à afficher des trucs ci-dessous. Il a juste changé sa police de caractères (???). On voit aussi clairement que ça consomme beaucoup de mémoire ce plan.

Les outils Windows ne s'attendant clairement pas à une machine avec 15000 processus mais les benchmarks donnent des résultats inchangés par rapport au niveau de référence.
Ah oui, si quelqu'un a vraiment copié mon script et lancé tout un tas de trucs avec "start", ces processus sont détachés de la console, il faut un autre script pour les TUER:
taskkill /F /IM many-threads-experiment.exe
Résultats en situation réelles
Il y a pas mal de ressources en ligne sur l'impact du bloatware (grogiciel... Gonflogiciel? Cacagiciel (sorry)) et en général ça se concentre sur les logiciels de contrôle RGB qui sont bien dans l'air du temps depuis quelques années.
J'ai une douleur fantôme aux tetons quand je pense à tous les programmes RGB qui sont nécessaires sur une machine qui avec des systèmes RGB différents puisque, évidemment, il n'y a aucun standard et chaque fabriquant fournit son logiciel de contrôle.
Gamers Nexus présente cette vidéo: https://www.youtube.com/watch?v=NzAeAFudylI
Où ils arrivent à perdre autour de 10% de performances par rapport au niveau de base en bourrant deux programmes de contrôle RGB et hwinfo64 et dans le pire des cas (GTA V). Sur d'autres jeux ils sont autour de 5% perdus.
Ils soulignent que les moyennes de FPS en jeu sont une piètre indication des possibles pics de ralentissement momentanés.
Leur impact mesuré pour des tests à 100% de CPU (il y en a très peu dans mes tests) atteint tout de même ~9%.
Tant que j'y suis, j'ai aussi regardé une vidéo de Linus TechTips (qui du coup dépasse le million de vues) où ils comparent une installation toute neuve de Windows 10 avec de surcroît quelques bidules désactivés histoire d'être plus propres que la crâne de Jean-Michel Propre (dit aussi M. Propre) à une version plus ancienne avec plusieurs programmes d'arrière plan (bien que je trouve perso que ça manque de RGB mais bon...).
Ils trouvent un maximum de 5% de différence pour CS:Go qui est typiquement le jeu qui va utiliser le plus le processeur si on ne limite pas les FPS (parce qu'on dépasse les 400 FPS) entre la référence et l'installation "SALE".
Détail intéressant: lors d'un test à l'aveugle sur Doom Eternal entre la machine propre et la SALE, il ne parvient pas les différencier. Ce qui n'est pas vraiment étonnant si on parle de moins de 5% de différences et qu'on est au delà de 100 FPS.
Il conclut également que l'utilisation mémoire est le double pour l'installation "sale" mais l'utilisation processeur n'a pas l'air abusive.
Notons bien qu'ils utilisent un Intel Core i7 11700k qui est moderne, 8 coeurs, et boost à presque 5 Ghz. Comme ils l'indiquent eux-mêmes dans la vidéo, au plus balaise est votre système, le moins d'impact sera ressenti par les bidules d'arrière-plan.
D'ailleurs j'aurais dû tester sur un vieux dual-core pour rigoler. Ce sera pour une autre fois 💨.
Discussion
Mes expériences montrent peu d'impact quand le processeur est à 100% alors que d'autres sources indiquent un impact notable.
Je pense que c'est la conséquence de "ce que fait le processus/thread", qui finalement est l'élément le plus impactant sur les performances.
Mes threads bizarres génèrent deux changements de contexte par seconde. Je me demandais combien le fameux "Corsair iCUE" en génère.
Je vais quand même pas installer ce truc, je veux bien sacrifier une partie de mon honneur pour la science mais là ça va trop loin, je vais l'installer dans une machine virtuelle.
Le programme d'installation pèse 980GB. Ouais. Très bon signe.
Comme je m'y attendais, on a droit à un processus principal en plus d'un mystérieux service:
Quand on y regarde de plus près avec Process Explorer, c'est pas beau à voir. Ici après seulement 20 minutes si je me souviens bien:

Tout ça pour un iCUE plutôt épuré sur ma machine virtuelle:
Vous pouvez comparer avec Windows Defender qui est typiquement un autre gros consommateur de ressources (y compris en entrée/sortie sur vos systèmes de stockage) et constater que iCUE l'emporte haut la main en utilisations de ressources.
Defender analyse les fichiers pour chercher des problèmes de sécurité, iCUE contrôle des lampes. Je crois.
Conclusion
Pour faire court, j'ai mis 1000 ans à écrire un article qui sert un peu à rien. QUI A DIT "COMME D'HABITUDE"??
D'un côté je suis légèrement dans l'embarras parce que mon à priori c'était que le nombre de threads (ou de processus) à lui seul aurait un effet sur les performances. Parfois on doit accepter que ses croyances soient brisées comme un pâté surgelé qui tombe du 4ème étage. C'est typiquement un truc qui ferait du bien à l'humanité mais ne nous emballons pas, n'est-ce pas (je parle de revoir ses croyances, pas du pâté).
Vous êtes peut-être en train de vous dire "ben évidemment... C'est ce que le thread fait qui compte pas combien qu'il y en a!" — Oui ben vous êtes plus malins que moi, félicitations, tirage de chapeau, arabesques sauvages, santé bonheur.
C'est sûr que quelques programmes qui demandent souvent du temps processeur ou d'autres ressources vont avoir bien plus d'impact que mes 15000 threads qui checkent un emplacement mémoire puis s'endorment.
Un moment je pensais innocemment que Firefox avait moins d'impact que Chrome parce qu'il utilise typiquement moins de threads/processus. Mais en fait non, avoir plus de processus ou threads ne change rien en soi à part que Chrome consomme plus de mémoire mais la plupart des gens semi-privilégiés de l'ordinateur s'en balancent à juste titre.
Peut-être que je peux calmer ma pshychose du nombre de processus et threads et arrêter d'écrire des scripts avec tout plein de net stop pour arrêter des services Windows, sans compter ce bidule que j'ai écrit pour remplacer ShadowPlay et qui en fait est très moyennement nécessaire (genre je le savais pas déjà lel)...
NAaaaaan, avoir moins de trucs ça accélère le démarrage de l'ordi (oui je fais partie des bobos qui arrêtent leur ordi quand ils ne l'utilise pas, je vous juge pas (un peu quand même)) et ça prend moins de mémoire, ça peut toujours servir la mémoire. Bon OK j'ai pas vraiment de problème de mémoire en dehors de ceux qui sont induits par la bière mais chuut.
Je pense avoir démontré qu'il était possible d'utiliser Process Explorer pour observer le nombre de changements de contexte d'un processus et ses threads, c'est assez indicatif d'à quel point ce processus demande de l'attention et serait un bon candidat pour un remplacement ou une bonne vieille désinstallation.
Sur ce, ce fût beaucoup trop long pour en arriver là, le prochain article sera juste plein de gifs et de memes et ne parlera peut-être pas de JavaScript. Si tout va bien.
Accessoirement, si mon article bizarre motive des recherches plus intéressantes que les miennes (devrait pas être super compliqué) j'en tirerai une moyenne à grande satisfaction.

















Commentaires
Il faut JavaScript activé pour écrire des commentaires ici