Guide pratique pour se rapprocher autant que possible du temps réel souple sur un Windows ordinaire
· Mis à jour le: · Go Komura · Développement Windows, Temps réel souple, Conception, Mesure
Quand on construit sous Windows un traitement où « être en retard pose problème » — traitement périodique, audio, vidéo, mesure, pilotage d’équipement —, l’impression que « Windows n’est pas vraiment fait pour ça » revient souvent. Cette impression est à moitié vraie et à moitié fausse : Windows n’est pas un OS hard real-time, mais si l’on travaille sérieusement la conception, l’implémentation, la mesure et l’exploitation, on peut l’amener à un état vraiment exploitable en tant que soft real-time.
Ce que couvre cet article, c’est un Windows 10 / 11 ordinaire, sans extension RTOS spéciale, sans pilote noyau maison, sans contrôleur dédié. C’est une discussion résolument pratique sur jusqu’où l’on peut faire baisser la latence et la gigue avec une application en mode utilisateur, sur un PC de bureau ou portable ordinaire. L’audio, la vidéo, le contrôle périodique et l’acquisition de données diffèrent dans le détail, mais les points sensibles sont largement communs ; nous avons donc rassemblé ici ce socle commun sous forme de check-list.
Table des matières
- La conclusion d’abord (en une phrase)
- Ce que « temps réel souple » signifie sur un Windows ordinaire
- 2.1. Ce que cet article entend par « Windows ordinaire »
- 2.2. Ce qui est possible, et où cela devient difficile
- 2.3. Un mot sur le vocabulaire, avant d’aller plus loin
- Les principales causes de latence et de gigue
- 3.1. L’ordonnanceur et les priorités
- 3.2. Les DPC / ISR et les pilotes
- 3.3. Les défauts de page et la mémoire
- 3.4. La résolution des minuteurs et la gestion de l’alimentation
- 3.5. La migration entre cœurs et la chaleur
- Check-list pratique pour réduire les retards sur un Windows ordinaire
- 4.1. Boucle périodique et méthode d’attente
- 4.2. Fast path / slow path et files d’attente de longueur fixe
- 4.3. Priorités / MMCSS / mode arrière-plan
- 4.4. Mémoire / GC / coûts au premier passage
- 4.5. Paramètres d’alimentation / EcoQoS / résolution des minuteurs
- 4.6. Placement CPU / migration entre cœurs / chaleur
- 4.7. Isoler les pilotes / DPC / ISR / perturbations externes
- Mesure et évaluation
- 5.1. Que faut-il enregistrer
- 5.2. Comment lire p99 / p99,9 / max
- 5.3. Avec quoi mesurer
- 5.4. Bonnes pratiques de test
- Guide de choix rapide
- Conclusion
- Références
1. La conclusion d’abord (en une phrase)
- Sur un Windows ordinaire, l’objectif n’est pas une garantie de hard real-time, mais une architecture soft real-time « peu susceptible d’être en retard, et qui ne casse pas quand elle l’est ».
- Le levier le plus efficace est de rendre le chemin critique (hot path) court, de longueur fixe et non bloquant.
- Séparez le fast path (acquisition / contrôle) du slow path (stockage / communication / UI), et reliez-les par une file d’attente de longueur fixe.
- Pilotez la boucle périodique par des échéances absolues, plutôt que de vous reposer sur
Sleep(1). - Pour les flux continus comme l’audio et la vidéo, envisagez d’abord MMCSS.
- Pour la mesure du temps, utilisez QueryPerformanceCounter (QPC) — en .NET,
Stopwatch. - Pour l’attente, privilégiez les événements de périphérique ou les waitable timers haute résolution.
- N’utilisez
timeBeginPeriodque le temps nécessaire. Ne concevez pas votre système en supposant qu’il est actif en permanence. - En exploitation réelle, l’alimentation secteur, le mode d’alimentation, la gestion d’EcoQoS et l’allègement de la charge en arrière-plan font tous une vraie différence.
- Évaluez non seulement la moyenne, mais aussi p99 (le seuil où le 1 % le plus lent commence à apparaître) / p99,9 / max / nombre d’échéances manquées / DPC / ISR / défauts de page / profondeur de file d’attente.
En résumé, sur un Windows ordinaire, réduire par la conception les raisons d’être en retard est plus efficace qu’augmenter les priorités. Les priorités et les paramètres d’alimentation comptent, mais ils ne suffisent pas à eux seuls à créer de la stabilité.
2. Ce que « temps réel souple » signifie sur un Windows ordinaire
2.1. Ce que cet article entend par « Windows ordinaire »
Par Windows ordinaire, nous entendons ici, en gros, les hypothèses suivantes.
- Un PC de bureau ou portable Windows 10 / 11 classique
- Aucune extension RTOS maison
- Aucun développement de pilote en mode noyau maison
- Une application normale en mode utilisateur
- Un ajustement au moyen des API et des paramètres Windows standard
Autrement dit, il ne s’agit pas de construire de toutes pièces une machine dédiée entièrement vouée au contrôle temps réel, mais de voir jusqu’où l’on peut réalistement aller sur un PC Windows ordinaire.
flowchart LR
A["PC Windows 10 / 11 ordinaire"] --> B["Application en mode utilisateur"]
B --> C["Viser le temps réel souple"]
C --> D["Réduire la latence"]
C --> E["Réduire la gigue"]
C --> F["Observer les échéances manquées sans casser le système"]
G["Besoin de garantir zéro dépassement de délai"] -.-> H["RTOS / contrôleur dédié / FPGA / traitement côté périphérique"]
2.2. Ce qui est possible, et où cela devient difficile
Même sur un Windows ordinaire, on peut construire un dispositif vraiment « rarement en retard » pour des traitements tels que les suivants.
- Traitement périodique de quelques millisecondes à quelques dizaines de millisecondes
- Audio / vidéo pilotés par tampon
- Acquisition de capteurs et boucles de contrôle
- Traitement à période fixe façon « soft PLC »
- Un pipeline à faible latence tournant sur un thread séparé de l’UI
Cela dit, « possible » ne signifie pas ici que l’on peut ramener à zéro absolu les pics de latence occasionnels. L’état que l’on vise est plutôt celui-ci.
- Réduire la latence en régime normal
- Réduire la gigue
- Ne pas casser le système quand une échéance est manquée de temps en temps
- Être capable d’observer le fait qu’elle a été manquée
À l’inverse, des exigences comme les suivantes deviennent très difficiles à satisfaire avec du seul mode utilisateur sur un Windows ordinaire.
- Garantir zéro dépassement de délai
- Tenir de façon stable en dessous de quelques centaines de microsecondes sur de longues durées
- Cohabiter avec une interface graphique lourde, du réseau et du stockage
- Le faire sur batterie ou en conservant des priorités d’économie d’énergie
- Ne tolérer aucun pic, même causé par les pilotes ou les périphériques
Pour ces cas, il est plus sûr d’envisager de déporter uniquement la partie réellement critique en temps vers un firmware côté périphérique, un contrôleur dédié, un FPGA ou un RTOS.
2.3. Un mot sur le vocabulaire, avant d’aller plus loin
Fixons d’abord le sens des termes utilisés dans cet article.
| Terme | En une phrase | Vue pratique |
|---|---|---|
| Temps réel souple (soft real-time) | Un retard occasionnel peut survenir ; l’approche consiste à le rendre petit et supportable | C’est ce qu’il faut viser en premier sur un Windows ordinaire |
| Temps réel strict (hard real-time) | Un monde où zéro dépassement de délai doit être garanti | Ce n’est pas un objectif atteignable en mode utilisateur seul sur un Windows ordinaire |
| Gigue (jitter) | Variation de la période ou du temps de réponse | Même avec une bonne moyenne, une forte gigue signifie de l’instabilité en exploitation réelle |
| Échéance manquée (deadline miss) | Le traitement ne se termine pas à l’heure prévue | Ne pas la cacher : la compter et la journaliser |
| p99 / p99,9 | Indicateurs pour observer la traîne lente | p99 est « le seuil où le 1 % le plus lent commence à apparaître » |
| DPC / ISR | Traitement côté noyau autour des pilotes et des interruptions | Quand c’est long, les threads en mode utilisateur sont mis en attente |
| MMCSS | Le mécanisme Windows qui alloue le CPU aux traitements sensibles au temps comme l’audio / la vidéo | Une option solide pour un traitement qui ne doit jamais laisser son tampon se vider |
| QPC | QueryPerformanceCounter |
La base de la mesure du temps écoulé — un compteur haute résolution, pas l’horloge murale |
3. Les principales causes de latence et de gigue
Sur un Windows ordinaire, les raisons pour lesquelles un traitement périodique prend du retard se ramènent presque toujours à l’une des cases de ce schéma.
flowchart TD
Late["Le traitement périodique prend du retard"] --> S["Ordonnanceur / priorités"]
Late --> D["DPC / ISR / pilotes"]
Late --> M["Défauts de page / mémoire"]
Late --> T["Résolution des minuteurs / gestion de l'alimentation"]
Late --> C["Migration entre cœurs / chaleur"]
3.1. L’ordonnanceur et les priorités
Les threads Windows s’exécutent dans un ordre déterminé par leur priorité. À priorité égale, ils tournent en round-robin, et lorsqu’un thread de priorité plus élevée devient exécutable, les threads de priorité inférieure sont mis de côté.
Ainsi, même en écrivant consciencieusement son thread périodique, il est tout à fait normal que les éléments suivants s’exécutent avant :
- D’autres threads
- D’autres processus
- Des traitements internes de l’OS
- Des logiciels de sécurité
- Des traitements auxiliaires de périphériques
- De la synchronisation en arrière-plan
3.2. Les DPC / ISR et les pilotes
Ce point est assez important. Même avec des priorités applicatives bien réglées, si des DPC (Deferred Procedure Call) ou des ISR (Interrupt Service Routine) s’exécutent longtemps, les threads en mode utilisateur ne peuvent pas s’exécuter pendant ce temps.
Les périphériques et pilotes qui en sont fréquemment la cause incluent :
- USB
- Wi-Fi / Bluetooth
- Stockage
- Audio
- GPU
- ACPI / gestion de l’alimentation
Même si le code applicatif n’a rien de fautif, on peut se retrouver bloqué à cause de circonstances liées au pilote ou au matériel. Se dire « il suffit d’augmenter la priorité de mon appli pour gagner » se termine généralement mal ici.
3.3. Les défauts de page et la mémoire
Si un défaut de page (page fault : une page nécessaire n’est pas en mémoire et doit être récupérée) survient sur le hot path, la latence explose instantanément.
Voici les schémas particulièrement à éviter.
- Commit de page lors du premier accès
- Chargement différé (lazy loading)
- Pagination (page-in) de fichiers mappés en mémoire
- Allocation dynamique plus importante que nécessaire
- Objets volumineux ou tas fragmenté
Pour le corps du traitement périodique, la bonne posture est grosso modo : allouer à l’avance la mémoire nécessaire, et la toucher une fois au démarrage.
3.4. La résolution des minuteurs et la gestion de l’alimentation
« Je veux tourner toutes les 1 ms, donc Sleep(1) » ne fonctionne presque jamais comme prévu.
La précision d’attente sous Windows est influencée par la résolution des minuteurs, l’ordonnancement et l’état d’alimentation.
De plus, il ne faut pas perdre de vue qu’augmenter la résolution des minuteurs améliore légèrement la précision d’attente, mais a des effets de bord sur la consommation d’énergie et le comportement global du système.
3.5. La migration entre cœurs et la chaleur
Quand un thread migre d’un cœur à un autre, les caches doivent se réchauffer à nouveau. L’OS gère souvent bien cela tout seul, mais sous forte charge, cela devient une source d’instabilité.
De plus, la chaleur devient non négligeable sur de longues durées de fonctionnement. Quand la limitation thermique (thermal throttling) se déclenche, une période jusque-là stable peut se désorganiser.
4. Check-list pratique pour réduire les retards sur un Windows ordinaire
Voici la partie pratique. Pour les causes vues dans la section précédente, nous résumons sous forme de check-list ce qu’il faut vérifier, ce qu’il faut éviter, et ce qu’il faut décider en premier sur un Windows ordinaire.
4.1. Boucle périodique et méthode d’attente
D’abord, voici l’anti-pattern classique.
while (running)
{
Sleep(1);
Step();
}
Ce n’est pas une « période de 1 ms », mais une boucle qui attend environ 1 ms ou plus, puis ajoute par-dessus le temps d’exécution de Step().
Pire, le dépassement d’attente s’accumule directement.
flowchart LR
subgraph Bad["Basé sur le temps relatif"]
B1["Sleep(1)"] --> B2["Step()"]
B2 --> B1
end
B2 --> B3["L'erreur d'attente et le temps d'exécution s'accumulent peu à peu"]
subgraph Good["Basé sur une échéance absolue"]
G1["next += period"] --> G2["WaitUntil(next - margin)"]
G2 --> G3["Spin court si nécessaire"]
G3 --> G4["FastStep()"]
G4 --> G1
end
G4 --> G5["Résiste à l'accumulation de dérive"]
Check-list
- La boucle périodique n’est pas construite sur
Sleep(1) - La période est pilotée par des échéances absolues via
next += period - L’attente privilégie un événement de périphérique ou un waitable timer
- Seul l’ajustement final utilise un très court busy-spin
timeBeginPeriodn’est utilisé que le temps nécessaire, puis restauré ensuite- Le comportement a été vérifié en état minimisé / masqué / non visible
Une boucle périodique est plus stable lorsqu’elle est pilotée par des échéances absolues plutôt que par du temps relatif.
int64_t next = QpcNow() + periodTicks;
while (running)
{
WaitUntil(next - wakeMarginTicks);
while (QpcNow() < next)
{
CpuRelax(); // Spin court, uniquement à la toute fin
}
int64_t started = QpcNow();
FastStep();
int64_t finished = QpcNow();
RecordTiming(next, started, finished);
next += periodTicks;
while (finished > next)
{
++missedDeadlines;
next += periodTicks;
}
}
4.2. Fast path / slow path et files d’attente de longueur fixe
La base architecturale consiste à ne placer sur le fast path que le travail sensible à l’échéance, et à repousser tout le reste vers le slow path.
flowchart LR
Input["Périphérique / événements d'acquisition"] --> Fast["fast path : acquisition, contrôle, copie minimale"]
Fast --> Queue["File d'attente de longueur fixe"]
Queue --> Slow["slow path : stockage, envoi, UI, agrégation"]
Fast --> Metrics["Enregistrer le retard / les échéances manquées / la profondeur de file"]
Metrics --> Slow
Limitez le fast path à peu près à ceci.
- Acquisition de données
- Calcul de la valeur de contrôle
- La copie strictement minimale nécessaire
- L’horodatage
- La mise en file d’attente
- L’enregistrement des échéances manquées / dépassements
Tout le reste retombe sur le slow path.
Check-list
- Aucune écriture de fichier, envoi réseau ou écriture en base de données sur le hot path
- Aucune journalisation lourde,
Flushou RPC synchrone sur le hot path - Fast path / slow path clairement séparés par thread ou par responsabilité
- La file d’attente est de longueur fixe
- La politique en cas de saturation de la file est décidée à l’avance
- Le nombre d’échéances manquées, le nombre d’éléments perdus (drop) et la profondeur de file sont observés
- Les mises à jour d’UI et l’agrégation des journaux sont séparées à une fréquence plus basse
Quand la file d’attente se remplit, il est plus sûr de ne pas laisser la politique dans le flou.
flowchart TD
Overflow["File d'attente pleine"] --> Policy{"Que faut-il protéger ?"}
Policy -->|La valeur la plus récente compte| Latest["Supprimer les anciens éléments, garder le plus récent"]
Policy -->|Chaque enregistrement compte| All["Alerter / arrêter / réguler en amont"]
Policy -->|Usage journalisation| Log["Supprimer les anciens éléments, ne conserver que le compteur de pertes"]
4.3. Priorités / MMCSS / mode arrière-plan
La règle de base pour les priorités est : ne pas tout augmenter. Sur un Windows ordinaire, « n’augmenter que les threads importants, et bien abaisser le travail de fond » fonctionne mieux. Le mode arrière-plan (background mode) est un mécanisme qui traite non seulement le CPU, mais aussi des ressources comme l’E/S, avec une priorité plus basse.
flowchart TD
Work["Répartir le travail"] --> Critical["Threads sensibles à l'échéance"]
Work --> Worker["Stockage / envoi / compression / agrégation"]
Work --> UI["UI"]
Critical --> P1["Priorité plus élevée ou MMCSS si nécessaire"]
Worker --> P2["Mode arrière-plan / priorité plus basse"]
UI --> P3["Priorité normale"]
P1 --> Warn["Ne pas commencer avec REALTIME_PRIORITY_CLASS"]
Check-list
- Tous les threads ne sont pas réglés en priorité élevée
- Seuls les threads réellement critiques en temps sont augmentés
- Le travail de fond (stockage, envoi, compression, synchronisation) est abaissé en mode arrière-plan
- MMCSS est envisagé pour les traitements en tampon continu comme l’audio, la vidéo, la capture et la lecture
- On raisonne d’abord par thread, avant de raisonner au niveau du processus entier
REALTIME_PRIORITY_CLASSn’est pas utilisé tant que le besoin n’est pas clairement établi
MMCSS (Multimedia Class Scheduler Service) est particulièrement efficace pour les traitements qui doivent « remplir un tampon dans un temps donné », comme l’audio / la vidéo. Cela s’accorde mieux avec la conception de Windows que de simplement faire tourner en permanence un thread à haute priorité.
Le code ressemble à peu près à ceci.
DWORD taskIndex = 0;
HANDLE avrt = AvSetMmThreadCharacteristicsW(L"Pro Audio", &taskIndex);
if (!avrt)
{
throw std::runtime_error("AvSetMmThreadCharacteristicsW failed");
}
// Exécuter la boucle sensible au temps
if (!AvRevertMmThreadCharacteristics(avrt))
{
throw std::runtime_error("AvRevertMmThreadCharacteristics failed");
}
4.4. Mémoire / GC / coûts au premier passage
Si vous utilisez new / malloc / List<T>.Add / la concaténation de chaînes / LINQ à chaque passage dans le hot path, les coûts de collecte et de réorganisation finiront par se faire sentir. Le GC (garbage collection, ramasse-miettes) n’est pas le coupable en soi, mais si vous écrivez du code qui alloue beaucoup, son impact remontera sous forme de gigue.
flowchart LR
Start["Démarrage"] --> Alloc["Allouer les tampons nécessaires"]
Alloc --> Touch["Les toucher une fois pour réchauffer les pages"]
Touch --> Warm["Faire passer le JIT / le chargement des DLL / la première E/S"]
Warm --> Measure["Ensuite seulement, faire la vraie mesure / le vrai fonctionnement"]
Check-list
- Aucune allocation / libération de mémoire à chaque itération sur le hot path
- Les tampons nécessaires sont préalloués au démarrage
- Les pages sont réchauffées en les touchant une fois au démarrage
- Le premier JIT, le premier chargement de DLL et la première E/S ne sont pas mélangés à la vraie mesure
- Aucune structure énorme ni journal de longueur variable qui grossit à l’intérieur de la boucle
- Si
VirtualLockest utilisé, il l’est uniquement sur une très petite zone critique
Vérifications côté .NET
- La mesure du temps utilise
Stopwatch/Stopwatch.GetTimestamp() - Aucun LINQ, concaténation de chaînes,
ToString()ou génération de journaux volumineux sur le hot path async/awaitn’est pas introduit dans le hot path- L’avant et l’après montée en température (warm-up) sont évalués séparément
4.5. Paramètres d’alimentation / EcoQoS / résolution des minuteurs
Ce point est discret, mais efficace. Même en ayant optimisé le code, les résultats ne se stabiliseront pas si un contrôle d’alimentation de niveau supérieur pèse fortement.
flowchart TD
Power["Gestion de l'alimentation sur un Windows ordinaire"] --> AC["Fonctionner sur secteur"]
Power --> Mode["Mode d'alimentation : orienté vers les meilleures performances"]
Power --> Plan["Un plan d'alimentation dédié à la production si nécessaire"]
Power --> QoS["Tenir les processus sensibles au temps à l'écart d'EcoQoS"]
Power --> Timer["Vérifier la façon dont les demandes de résolution de minuteur sont traitées"]
Check-list
- L’évaluation en production se fait d’abord sur alimentation secteur
Paramètres > Système > Alimentation et batterie > Mode d'alimentationest réglé vers Meilleures performances- Le mode économie de batterie / priorité à l’économie d’énergie n’est pas actif pendant l’exécution
- Les modes silencieux / eco / priorité batterie des utilitaires propres au fabricant ont été vérifiés
- Les processus sensibles au temps ne sont pas placés à la légère sous EcoQoS (QoS orienté économie d’énergie)
IGNORE_TIMER_RESOLUTIONn’est pas activé sur le processus sensible au temps- Il a été vérifié si l’effet des demandes de résolution de minuteur change en état minimisé / masqué
- Les paramètres d’alimentation pour l’usage courant et pour la production / la mesure / la démonstration sont séparés
timeBeginPeriod est utile si on l’utilise de manière organisée, mais ce n’est pas une solution miracle.
- Appelez-le juste avant qu’il ne soit nécessaire
- Restaurez-le avec
timeEndPeriodune fois terminé - Depuis Windows 10 version 2004, il n’a plus le comportement pleinement global d’autrefois
- Sur Windows 11, un processus dont les fenêtres sont totalement masquées / minimisées / non visibles / non audibles peut ne pas bénéficier d’une résolution élevée garantie
- Augmenter la résolution n’augmente pas la précision de QPC
Si l’on soupçonne un effet lié à l’alimentation ou au QoS, on peut vérifier l’état de la limitation d’alimentation (power throttling) avec SetProcessInformation.
PROCESS_POWER_THROTTLING_STATE state{};
state.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
state.ControlMask =
PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
state.StateMask = 0; // HighQoS (orienté performance) + respecter les demandes de résolution de minuteur
if (!SetProcessInformation(
GetCurrentProcess(),
ProcessPowerThrottling,
&state,
sizeof(state)))
{
throw std::runtime_error("SetProcessInformation failed");
}
4.6. Placement CPU / migration entre cœurs / chaleur
Pour le placement CPU, plutôt que de passer directement à un épinglage sur des cœurs spécifiques (hard affinity / CPU pinning), il est généralement préférable de commencer par quelque chose qui se rapproche d’une soft affinity — « merci de tourner surtout sur ces cœurs ».
flowchart LR
Measure["Mesurer d'abord"] --> Ideal["SetThreadIdealProcessor / CPU Sets"]
Ideal --> Check{"Amélioration suffisante ?"}
Check -->|Oui| Keep["S'arrêter là"]
Check -->|Non| Hard["Envisager SetThreadAffinityMask en dernier recours"]
Measure --> Therm["Vérifier aussi la température / la fréquence / les longues durées"]
Check-list
- Le placement CPU n’est modifié qu’après avoir mesuré
- On n’épingle pas d’emblée sur des cœurs spécifiques
SetThreadIdealProcessorou CPU Sets sont essayés en premierSetThreadAffinityMaskest traité comme un dernier recours- La température, la fréquence et la limitation thermique sont vérifiées sur de longues durées de fonctionnement
- Les modes silencieux / faible bruit des portables sont vérifiés
L’ordre suivant est le plus sûr.
- Mesurer d’abord
- Si nécessaire, ideal processor / CPU Sets
- Si une amélioration est encore nécessaire, épingler sur des cœurs spécifiques
L’épinglage sur des cœurs spécifiques semble efficace, mais il réduit les marges de manœuvre de l’OS, et utilisé à la légère, il peut au contraire rendre le système moins flexible.
4.7. Isoler les pilotes / DPC / ISR / perturbations externes
Quand « seul le max explose de temps en temps » ou que « la moyenne est bonne mais p99,9 est mauvais », il vaut la peine de suspecter des perturbations extérieures au-delà de son propre code.
flowchart TD
Spike["Un pic de retard / d'échéance manquée / de max est apparu"] --> Q1{"Le temps de votre propre traitement est-il aussi long ?"}
Q1 -->|Oui| App["Raccourcir le hot path / réduire les allocations / retirer les E/S"]
Q1 -->|Non| Q2{"Y a-t-il des pics DPC / ISR ?"}
Q2 -->|Oui| Driver["Vérifier USB / Wi-Fi / Bluetooth / GPU / audio / stockage / ACPI / mises à jour de pilotes"]
Q2 -->|Non| Q3{"Y a-t-il des défauts de page / du GC / des coûts au premier passage ?"}
Q3 -->|Oui| Mem["Préallouer / faire le warm-up / réduire la pression sur le tas"]
Q3 -->|Non| Q4{"Y a-t-il un effet batterie / économie d'énergie / thermique ?"}
Q4 -->|Oui| Pow["Alimentation secteur / paramètres d'alimentation / refroidissement / tests de longue durée"]
Q4 -->|Non| ETW["Creuser davantage avec ETW / WPA / LatencyMon"]
Check-list
- Les pilotes autour du Wi-Fi / Bluetooth / USB / stockage / GPU / audio ont été vérifiés
- Comparaison effectuée en arrêtant la synchronisation cloud, l’indexation et les mises à jour automatiques inutiles
- Vérifié également si le système se dégrade en état minimisé ou écran éteint
- Les tendances DPC / ISR examinées avec LatencyMon ou ETW
- « Mon traitement est lourd » vs « je suis bloqué de l’extérieur » examinés séparément
5. Mesure et évaluation
5.1. Que faut-il enregistrer
Au minimum, voici ce qu’il faut capturer.
- Heure de période prévue
- Heure de début réelle
- Heure de fin réelle
- Retard (lateness : de combien le début réel a été en retard par rapport au prévu)
- Temps d’exécution
- Nombre d’échéances manquées
- Nombre d’échéances manquées consécutives
- Profondeur de la file d’attente
- Nombre d’éléments perdus (drop)
- Utilisation CPU
- Déséquilibre entre cœurs
- Pics DPC / ISR
- Défauts de page
- Variation de température / de fréquence
Ne regarder que la moyenne rend l’essentiel difficile à saisir. Ce qui pose problème en production, ce sont les gros pics de latence occasionnels.
5.2. Comment lire p99 / p99,9 / max
Des indicateurs comme p99 existent pour observer la traîne lente. La moyenne seule cache les grands retards occasionnels.
| Indicateur | Signification | Intuition sur 10 000 mesures |
|---|---|---|
| Moyenne | La valeur lissée sur l’ensemble | Les pics s’y noient facilement |
| p50 | La valeur médiane | Proche du ressenti habituel |
| p95 | Le seuil où le 5 % le plus lent commence à apparaître | La limite excluant les 500 mesures les plus lentes |
| p99 | Le seuil où le 1 % le plus lent commence à apparaître | La limite excluant les 100 mesures les plus lentes |
| p99,9 | Le seuil où le 0,1 % le plus lent commence à apparaître | La limite excluant les 10 mesures les plus lentes |
| max | La pire valeur | La seule mesure la plus lente |
Par exemple, avec :
- Moyenne : 0,8 ms
- p99 : 1,2 ms
- p99,9 : 3,5 ms
- max : 28 ms
le constat est : généralement rapide, mais avec de gros pics occasionnels. Sur un Windows ordinaire, les vrais problèmes se trouvent presque toujours dans cette traîne allant de p99 au max.
5.3. Avec quoi mesurer
L’outillage est assez standard.
- Mesure applicative interne
D’abord, capturer soi-même
period / lateness / execution time / queue depth / drop - ETW / WPR / WPA Creuser le CPU, les changements de contexte, les DPC / ISR, les défauts de page
- LatencyMon Repérer l’instabilité liée aux pilotes
- Surveillance de la température / de la fréquence Observer les effets thermiques
flowchart LR
App["Mesure applicative interne"] --> Dist["p50 / p95 / p99 / p99,9 / max"]
App --> Miss["échéances manquées / pertes / profondeur de file"]
ETW["ETW / WPR / WPA"] --> Root["changements de contexte / DPC / ISR / défauts de page"]
Temp["Surveillance de la température / de la fréquence"] --> Root
Dist --> Decide["Prioriser les améliorations"]
Miss --> Decide
Root --> Decide
Aller jusqu’à WPA demande un peu d’effort, mais c’est très efficace pour distinguer si les DPC / ISR sont la cause, ou si c’est simplement votre propre traitement qui est lourd.
5.4. Bonnes pratiques de test
Un environnement de banc d’essai silencieux seul ne suffit pas pour tester. Au minimum, il faut examiner séparément les conditions suivantes.
- Juste après le démarrage, avant le warm-up
- Après le warm-up
- Fonctionnement continu de longue durée
- UI au premier plan
- UI minimisée / proche de l’état masqué
- Alimentation secteur
- Alimentation sur batterie
- Avec une charge sur le réseau ou le disque
Évaluer uniquement sur banc d’essai rend facile de manquer les problèmes qui apparaissent en exploitation réelle. Le comportement d’un Windows ordinaire est facilement tiré par « la manière dont la machine est utilisée », il est donc important de vérifier dans des conditions proches de l’usage réel.
6. Guide de choix rapide
-
Classe 10-20 ms, une instabilité occasionnelle est absorbable → Séparation fast/slow, files d’attente de longueur fixe, priorité normale à légèrement élevée, et une conception pilotée par événements suffisent souvent
-
Classe 1-5 ms, il faut tenir la cadence en continu → Hot path sans allocation, threads dédiés, MMCSS ou ajustement soigneux des priorités, waitable timers haute résolution, alimentation secteur, paramètres d’alimentation orientés meilleures performances
-
On s’approche de moins de 1 ms, et on ne doit rien manquer même en longue durée et sous forte charge → Très difficile avec le seul mode utilisateur sur un Windows ordinaire. Envisager une conception qui déporte la partie critique ailleurs
-
Interface graphique / journalisation / communication / base de données, tout cohabitant → Ne pas tout entasser dans « un seul processus, une seule boucle » — séparer les responsabilités. Les contraintes en aval cassent facilement les échéances en amont
7. Conclusion
Deux prémisses méritent d’être retenues.
- Sur un Windows ordinaire, l’objectif n’est pas une garantie de hard real-time, mais une architecture soft real-time : une latence et une gigue faibles, et l’absence de casse quand un dépassement de délai survient
- Le plus gros gain vient de la mise en ordre du hot path, plus que du réglage des priorités
Côté implémentation, voici ce qui paie :
- Séparer le fast path et le slow path
- Utiliser des files d’attente de longueur fixe, et décider la politique de débordement à l’avance
- Mesurer avec QPC ; attendre avec des événements / waitable timers
- Éviter les allocations, les E/S bloquantes et les verrous lourds sur le hot path
Côté exploitation, voici ce qui paie :
- Fonctionner sur alimentation secteur
- Conserver une configuration d’alimentation séparée pour la production
- Réduire la charge inutile en arrière-plan
- Évaluer avec p99 / p99,9 / max et le nombre d’échéances manquées
Le temps réel souple sur un Windows ordinaire ne se décide pas uniquement par les réglages de priorité — en travaillant séparément la conception, l’implémentation, les paramètres d’alimentation, la mesure et l’exploitation, on peut construire un système remarquablement stable.
8. Références
- Multimedia Class Scheduler Service
- AvSetMmThreadCharacteristicsW function
- SetThreadPriority function
- SetPriorityClass function
- timeBeginPeriod function
- CreateWaitableTimerExW function
- Acquiring high-resolution time stamps
- QueryPerformanceCounter function
- GetSystemTimePreciseAsFileTime function
- SetProcessInformation function
- VirtualLock function
- CPU Sets
- SetThreadIdealProcessor function
- SetThreadAffinityMask function
- Processor power management options
- Change the power mode for your Windows PC
- Power settings in Windows 11
- CPU Analysis (WPA / WPT)
Articles associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Introduction à l'ADR (Architecture Decision Record) — la méthode minimale pour conserver « pourquoi on a choisi cette conception » sur un petit projet
Le code ne dit jamais pourquoi il a été écrit ainsi. Nous expliquons comment utiliser l'ADR (Architecture Decision Record) — un fichier M...
Migrer une application Windows vers le Web : les cas à éviter — tableau de décision et la solution réaliste du « fractionnement »
Les demandes de migration d'applications Windows internes vers le Web se multiplient, mais pour les applications reposant sur l'intégrati...
Pourquoi préférer l'attente sur événement à Sleep(1) sous Windows
Sous Windows, la précision des attentes courtes avec délai (timed wait) est limitée par la granularité de l'horloge système et par l'ordo...
Un tableau de décision pour choisir entre arrêt et poursuite après une exception inattendue
Lorsqu'une exception inattendue survient, faut-il arrêter l'application ou la laisser continuer ? Cet article organise la décision sous l...
Liste de contrôle minimale de sécurité pour le développement d'applications Windows
Pour les applications métier WPF / WinForms / WinUI / C++ / C#, cet article organise sous forme de liste de contrôle les bases concernant...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Applications métier, intégration d'équipements et outils de communication, des besoins au développement.
Conseil technique et revue de conception
Clarification de la stratégie de modification, de la conception et du traitement des actifs existants.
Questions fréquentes
Questions souvent posées lors d’une consultation sur le sujet de cet article.
- Peut-on faire du traitement en temps réel sous Windows ?
- Il n'est pas possible de garantir le hard real-time (zéro dépassement de délai), mais en travaillant sérieusement la conception, l'implémentation, la mesure et l'exploitation, on peut atteindre un état très concret de soft real-time. Un traitement périodique de quelques millisecondes à quelques dizaines de millisecondes, un pilotage par tampon pour l'audio/vidéo, ou une acquisition de capteurs avec boucle de contrôle sont tout à fait réalistes sur un Windows 10/11 ordinaire. À l'inverse, si vous avez besoin d'une garantie de zéro dépassement de délai ou d'une stabilité de longue durée en dessous de quelques centaines de microsecondes, il faut envisager de déporter cette partie vers un RTOS, un contrôleur dédié, un FPGA ou un traitement côté périphérique.
- Pourquoi ne faut-il pas utiliser Sleep(1) dans une boucle périodique ?
- Parce que Sleep(1) ne produit pas une période de 1 ms : il attend en réalité environ 1 ms ou plus, puis ajoute par-dessus le temps d'exécution du traitement, et ce dépassement d'attente s'accumule directement. Une boucle périodique est plus stable lorsqu'elle est pilotée par des échéances absolues avec next += period, que l'attente privilégie un événement de périphérique ou un waitable timer haute résolution, et que seul l'ajustement final utilise un très court busy-spin. timeBeginPeriod ne doit être utilisé que le temps nécessaire, puis restauré ensuite.
- Qu'est-ce qui est le plus efficace pour réduire la latence et la gigue ?
- Plutôt que d'augmenter les priorités, il est plus efficace de rendre le chemin critique (hot path) court, de longueur fixe et non bloquant. Séparez le fast path (acquisition / contrôle) du slow path (stockage / communication / UI) et reliez-les par une file d'attente de longueur fixe ; sur le hot path, évitez l'écriture de fichiers, l'envoi réseau, la journalisation lourde et les allocations mémoire à chaque itération. Côté exploitation, l'alimentation secteur, la révision du mode d'alimentation, la vérification d'EcoQoS et la réduction de la charge en arrière-plan sont également payantes.
- Avec quels indicateurs faut-il évaluer la stabilité d'un traitement périodique ?
- Ne vous fiez pas seulement à la moyenne : observez p99, p99,9, max et le nombre d'échéances manquées (missed deadlines). Par exemple, avec une moyenne de 0,8 ms mais un max de 28 ms, cela signifie que le système est généralement rapide mais présente parfois de gros pics ; sur un Windows ordinaire, les vrais problèmes se trouvent justement dans cette traîne allant de p99 au max. Enregistrez également les pics DPC/ISR, les page faults, la profondeur de la file d'attente et les variations de température/fréquence, et évaluez séparément des conditions comme avant/après la montée en température (warm-up), le fonctionnement en longue durée, l'état minimisé ou l'alimentation sur batterie.
Profil de l’auteur
Page de présentation de l’auteur de l’article.
Go Komura
Représentant de KomuraSoft LLC
Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.
Liens publics