Una guida pratica per avvicinarsi il più possibile a Soft Real-Time su Windows ordinario
· Aggiornato il: · Go Komura · Sviluppo Windows, Soft real-time, Progettazione, Misurazione
Quando si crea l’elaborazione su Windows dove “essere in ritardo è un problema” - elaborazione periodica, audio, video, misurazione, controllo dell’attrezzatura - tende ad emergere l’impressione che “Windows non può davvero farlo”. Questa impressione è per metà giusta e per metà sbagliata: Windows non è un hard real-time OS, ma se definisci correttamente progettazione, implementazione, misurazione e operazioni, puoi portarlo a uno stato genuinamente pratico come soft real-time.
Ciò che tratta in questo articolo è Windows 10 / 11 ordinario, senza estensioni speciali RTOS, driver del kernel personalizzati o controller dedicati. Si tratta di una discussione orientata alla pratica su quanto è possibile ridurre la latenza e il jitter con un’app in modalità utente su un PC desktop o portatile per l’uso quotidiano. Audio, video, controllo periodico e acquisizione dati differiscono nei dettagli, ma i punti problematici sono ampiamente condivisi, quindi questa volta abbiamo raccolto questo terreno comune sotto forma di una lista di controllo.
Sommario
- Prima la conclusione (in una riga)
- 1.1. Tabella rapida per intervallo periodico
- Cosa significa “Soft Real-Time” su Windows ordinario
- 2.1. Cosa significa questo articolo per “Windows ordinario”
- 2.2. Cosa è realizzabile e dove diventa difficile
- 2.3. Una breve parola sulla terminologia
- Le principali cause di latenza e jitter
- 3.1. Lo scheduler e le priorità
- 3.2. DPC / ISR e driver
- 3.3. Errori di pagina e memoria
- 3.4. Risoluzione del timer e gestione dell’alimentazione
- 3.5. Migrazione del nucleo e calore
- Una lista di controllo pratica per ridurre i ritardi sul Windows ordinario
- 4.1. Cicli periodici e come aspettare
- 4.2. Percorso Veloce / Percorso Lento e Code a Lunghezza Fissa
- 4.3. Priorità / MMCSS / Modalità Background
- 4.4. Memoria / GC / Costi di prima esecuzione
- 4.5. Impostazioni di alimentazione / EcoQoS / Risoluzione timer
- 4.6. Posizionamento CPU / Migrazione core / Riscaldamento
- 4.7 Isolamento dei driver / DPC / ISR / Disturbi esterni
- Misurazione e valutazione
- 5.1. Cosa registrare
- 5.2. Come leggere p99 / p99.9 / max
- 5.3. Con cosa misurare
- 5.4. Disciplina di prova
- Criteri pratici per decidere
- 6.1. Quando giudicare “con Windows ordinario non basta”
- 6.2. Quando far coesistere GUI / logging / comunicazione / DB
- Conclusione
- Riferimenti
1. Prima la conclusione (in una riga)
- Ciò che miri a Windows ordinario non è una garanzia hard real-time, ma una configurazione soft real-time che “è improbabile che arrivi in ritardo e non si rompa quando lo è.”
- La vittoria più grande è stata rendere il percorso caldo breve, di lunghezza fissa e non bloccante.
- Separare il percorso veloce (acquisizione / controllo) dal percorso lento (storage / comunicazione / UI) e collegarli con una coda a lunghezza fissa.
- Guidare il ciclo periodico su scadenze assolute, non appoggiandosi a
Sleep(1). - Per flussi continui come audio e video, considera prima MMCSS.
- Per la misurazione del tempo, utilizzare QueryPerformanceCounter (QPC) - in .NET,
Stopwatch. - Per l’attesa, preferisci eventi dispositivo o timer di attesa ad alta risoluzione.
- Utilizzare
timeBeginPeriodsolo per il tempo necessario. Non progettare partendo dal presupposto che sia sempre attivo. - Nel funzionamento reale, l’alimentazione CA / la modalità di alimentazione / come viene gestito l’EcoQoS / la riduzione del carico causato dalle attività in background valgono lo sforzo.
- Valuta non solo le medie, ma p99 (la soglia in cui inizia a mostrare il valore 1 su 100 più lento) / p99.9 / max / conteggio errori / DPC / ISR / errori di pagina / profondità della coda.
In breve, nel Windows ordinario, ridurre le ragioni del ritardo attraverso la progettazione batte l’innalzamento delle priorità. Le priorità e le impostazioni di alimentazione contano, ma da sole non possono creare stabilità.
1.1. Tabella rapida per intervallo periodico
Questo è un articolo lungo, quindi inseriamo una tabella rapida all’inizio per poter leggere solo le righe pertinenti al proprio caso. Prima era in fondo (sezione 6); l’abbiamo anticipata qui.
| Intervallo / Requisito | Configurazione di prima battuta | Sezione da leggere |
|---|---|---|
| 10-20 ms, con assorbimento di oscillazioni occasionali | Separazione fast path / slow path, code a lunghezza fissa, priorità normale-leggermente alta, progettazione guidata dagli eventi. Spesso basta | 4.1, 4.2 |
| 1-5 ms, da sostenere continuamente | In aggiunta a quanto sopra: percorso caldo senza allocazioni, thread dedicati, MMCSS o attenta regolazione delle priorità, timer di attesa ad alta risoluzione, alimentazione CA e revisione delle impostazioni energetiche | 4.1 - 4.5 |
| Meno di 1 ms, da rispettare anche a lungo e sotto carico | Molto difficile con la sola modalità utente su Windows ordinario. Progetta prima di spostare la parte critica altrove (firmware del dispositivo, controller dedicato, FPGA, RTOS) | 2.2, 6 |
| GUI / logging / comunicazione / DB tutti insieme | Non stipare tutto in “un processo, un ciclo”: separa le responsabilità. Le problematiche a valle superano facilmente le scadenze a monte | 4.2, 4.3, 6 |
La diagnosi delle cause e la disciplina di prova sono comuni a ogni intervallo (capitoli 3 e 5).
2. Cosa significa “Soft Real-Time” su Windows ordinario
2.1. Cosa significa questo articolo per “Windows ordinario”
Per Windows ordinario assumiamo approssimativamente quanto segue.
- Un tipico PC desktop o portatile Windows 10 / 11
- Nessuna estensione RTOS personalizzata
- Nessuno sviluppo di driver personalizzati in modalità kernel
- Una normale app in modalità utente
- Sintonizzazione con Windows API standard e impostazioni
In altre parole, non si tratta non di costruire un’intera macchina dedicata per il controllo in tempo reale: si tratta di quanto lontano puoi realisticamente spingere le cose su un normale PC Windows.
flowchart LR
A["PC Windows 10 / 11 normale"] --> B["App in modalità utente"]
B --> C["Punta a soft real-time"]
C --> D["Mantieni bassa la latenza"]
C --> E["Mantieni il jitter basso"]
C --> F["Rispettare le scadenze mancate ed evitare rotture"]
G["Necessità di garantire zero scadenze non rispettate"] -.-> H["RTOS / controller dedicato / FPGA / elaborazione lato dispositivo"]
2.2. Cosa è realizzabile e dove diventa difficile
Anche in Windows ordinario, puoi creare una configurazione veramente “raramente tardiva” per l’elaborazione come la seguente.
- Elaborazione periodica da pochi millisecondi a decine di millisecondi
- Audio / video gestito da buffer
- Acquisizione sensori e anelli di controllo
- Elaborazione a periodo fisso in stile SoftPLC
- Una pipeline a bassa latenza in esecuzione su un thread separato dall’UI
Detto questo, “realizzabile” in questo caso non significa il picco occasionale di latenza può essere ridotto allo zero assoluto. Lo stato a cui miriamo è questo:
- Mantieni bassa la latenza nel caso normale
- Mantieni il jitter basso
- Non interrompere quando occasionalmente una scadenza viene mancata
- Essere in grado di osservare il fatto che è mancato
Al contrario, requisiti come i seguenti diventano molto difficili da soddisfare con la sola modalità utente su Windows ordinario.
- Garantire zero scadenze mancate
- Mantenere stabilmente al di sotto di alcune centinaia di microsecondi per lunghi periodi
- Coesistere con una GUI pesante, una rete pesante e un storage pesante
- Farlo con l’alimentazione a batteria o con le priorità di risparmio energetico intatte
- Non tollerare nemmeno picchi causati da driver o dispositivi
Per questi, è più sicuro considerare anche di spostare solo la parte veramente critica in termini di tempo nel firmware lato dispositivo, in un controller dedicato, in un FPGA o in un RTOS.
2.3. Una breve parola sulla terminologia
Innanzitutto definiamo i termini utilizzati in questo articolo.
| Termine | In una riga | Visione pratica |
|---|---|---|
| Soft real-time | Possono verificarsi ritardi occasionali; l’approccio è renderlo piccolo e sostenibile | Questo è l’obiettivo a cui puntare innanzitutto su Windows ordinario |
| Hard real-time | Un mondo in cui bisogna garantire zero scadenze mancate | Non è un obiettivo per la sola modalità utente su Windows ordinario |
| Jitter | Variazione del periodo o del tempo di risposta | Anche con una buona media, un jitter elevato significa instabilità nel funzionamento reale |
| Scadenza mancata | L’elaborazione non termina entro l’orario previsto | Non nasconderlo: contalo e registralo |
| p99 / p99.9 | Metriche per osservare la coda lenta | p99 è “la soglia in cui inizia a comparire l’1 su 100 più lento” |
| DPC / ISR | Elaborazione lato kernel attorno ai driver e interrupts | Quando è lungo, i thread in modalità utente vengono fatti attendere |
| MMCSS | Il meccanismo Windows che alloca la CPU per lavori urgenti come audio / video | Una forte opzione per l’elaborazione che non deve mai affamare i propri buffer |
| QPC | QueryPerformanceCounter |
La base per la misurazione del tempo trascorso: un contatore ad alta risoluzione, non l’orologio da parete |
3. Le principali cause di latenza e jitter
I motivi per cui l’elaborazione periodica resta indietro rispetto a Windows ordinario sono quasi sempre riconducibili a una delle caselle di questo diagramma.
flowchart TD
Late["L'elaborazione periodica resta indietro"] --> S["Pianificatore / priorità"]
Late --> D["DPC / ISR / driver"]
Late --> M["Errori di pagina / memoria"]
Late --> T["Risoluzione timer / gestione alimentazione"]
Late --> C["Migrazione / calore del nucleo"]
3.1. Lo scheduler e le priorità
I thread Windows vengono eseguiti in ordine di priorità. A pari priorità si alternano in round robin e quando un thread con priorità più alta diventa eseguibile, i thread con priorità più bassa vengono messi da parte.
Quindi, anche se scrivi diligentemente il tuo thread periodico, è del tutto normale che quanto segue venga eseguito per primo:
- Altri thread
- Altri processi
- lavoro interno dell’OS
- Prodotti per la sicurezza
- Elaborazione dell’assistente del dispositivo
- Sincronizzazione in background
3.2. DPC / ISR e driver
Questa parte è piuttosto importante. Anche con le priorità lato app in ordine, se DPC (chiamate di procedura differite) o ISR (Interrupt routine di servizio) durano a lungo, i thread in modalità utente non possono essere eseguiti durante quel periodo.
I dispositivi e i driver che comunemente causano questo problema includono:
- USB
- Wi-Fi / Bluetooth
- Storage
- Audio
- GPU
- ACPI / gestione dell’alimentazione
Anche quando il codice dell’applicazione va bene, puoi rimanere bloccato a causa di circostanze del driver o dell’hardware. Pensare “Aumenterò semplicemente la priorità della mia app e vincerò” qui di solito finisce in dolore.
3.3. Errori di pagina e memoria
Se si verifica un errore di pagina (una pagina necessaria che non è in memoria e deve essere recuperata) sull’hot path, la latenza aumenta immediatamente.
Modelli particolarmente da evitare:
- Commit della pagina al primo accesso
- Caricamento lento
- Paginazione dei file mappati in memoria
- Allocazione più dinamica del necessario
- Oggetti di grandi dimensioni o heap frammentato
Per il corpo dell’elaborazione periodica, la postura corretta è più o meno: allocare la memoria necessaria in anticipo e toccarla una volta all’avvio.
3.4. Risoluzione del timer e gestione dell’alimentazione
“Voglio eseguire ogni 1 ms, quindi Sleep(1)” non funziona quasi mai.
La precisione dell’attesa Windows è influenzata dalla risoluzione del timer, dalla pianificazione e dallo stato di alimentazione.
Inoltre, non trascurare che l’aumento della risoluzione del timer migliora leggermente la precisione dell’attesa ma ha effetti collaterali sul consumo energetico e sul comportamento generale del sistema.
3.5. Migrazione del nucleo e calore
Quando un thread migra tra i core, le cache devono riscaldarsi nuovamente. L’OS spesso se la cava bene da solo, ma sotto carico pesante diventa fonte di oscillazioni.
Anche il calore diventa non trascurabile sui lunghi periodi. Quando entra in azione la limitazione termica, un periodo precedentemente stabile può andare in pezzi.
4. Una lista di controllo pratica per ridurre i ritardi nel Windows ordinario
Qui inizia la parte pratica. Per le cause viste nella sezione precedente, riassumiamo cosa controllare, cosa evitare e cosa decidere prima sul Windows ordinario, sotto forma di checklist.
4.1. Cicli periodici e come aspettare
Innanzitutto il classico anti-pattern è questo.
while (running)
{
Sleep(1);
Step();
}
Questo non è un “periodo di 1 ms”: è un ciclo che attende circa 1 ms o più, quindi aggiunge in cima il tempo di esecuzione di Step().
Quel che è peggio, il superamento dell’attesa si accumula direttamente.
flowchart LR
subgraph Bad["Basato sul tempo relativo"]
B1["Sleep(1)"] --> B2["Step()"]
B2 --> B1
end
B2 --> B3["L'errore di attesa e il tempo di esecuzione si accumulano poco a poco"]
subgraph Good["Basato sulla scadenza assoluta"]
G1["next += period"] --> G2["WaitUntil(next - margin)"]
G2 --> G3["Se necessario, centrifuga breve"]
G3 --> G4["FastStep()"]
G4 --> G1
end
G4 --> G5["Resiste all'accumulo di deriva"]
Lista di controllo
- Il ciclo periodico non è costruito su
Sleep(1) - Il periodo è guidato da scadenze assolute tramite
next += period - L’attesa preferisce eventi dispositivo o timer di attesa
- Solo la regolazione fine finale utilizza un giro occupato molto breve
timeBeginPeriodviene utilizzato solo quando necessario e ripristinato in seguito- Il comportamento è stato verificato mentre era ridotto a icona / nascosto / non visibile
Un ciclo periodico è più stabile se guidato da scadenze assolute piuttosto che da tempo relativo.
int64_t next = QpcNow() + periodTicks;
while (running)
{
WaitUntil(next - wakeMarginTicks);
while (QpcNow() < next)
{
CpuRelax(); // Spin briefly only at the very end
}
int64_t started = QpcNow();
FastStep();
int64_t finished = QpcNow();
RecordTiming(next, started, finished);
next += periodTicks;
while (finished > next)
{
++missedDeadlines;
next += periodTicks;
}
}
4.2. Percorso Veloce / Percorso Lento e Code a Lunghezza Fissa
La base dell’architettura è mettere solo il lavoro sensibile alle scadenze sul percorso veloce e spingere tutto il resto sul percorso lento.
flowchart LR
Input["Eventi del dispositivo / acquisizione"] --> Fast["percorso veloce: acquisizione, controllo, copia minima"]
Fast --> Queue["Coda a lunghezza fissa"]
Queue --> Slow["percorso lento: archiviazione, invio, UI, aggregazione"]
Fast --> Metrics["Registra ritardi / manchi / profondità della coda"]
Metrics --> Slow
Limitare il percorso veloce a più o meno questo valore.
- Acquisizione dati
- Calcolo del valore di controllo
- La copia minima necessaria
- Timestamp
- Accodamento
- Registrazione di mancati / superamenti
Tutto il resto cade sul percorso lento.
Lista di controllo
- Nessun file scrive, invia in rete o scrive DB sul percorso attivo
- Nessuna registrazione pesante,
Flusho RPC sincrono sul percorso attivo - Percorso veloce / percorso lento chiaramente separati da filo conduttore o responsabilità
- La coda è a lunghezza fissa
- La politica per l’overflow della coda viene decisa in anticipo
- Vengono osservati i conteggi mancati, i conteggi eliminati e la profondità della coda
- Gli aggiornamenti dell’UI e l’aggregazione dei log vengono separati con una frequenza inferiore
Quando la coda si riempie, è più sicuro non lasciare la policy nel vago.
flowchart TD
Overflow["La coda è piena"] --> Policy{"Cosa proteggiamo?"}
Policy -->|Latest value matters| Latest["Elimina le vecchie voci e mantieni le ultime"]
Policy -->|Every record matters| All["Avviso / arresto / limitazione a monte"]
Policy -->|Logging use| Log["Elimina le vecchie voci, registra solo il conteggio delle cadute"]
4.3. Priorità / MMCSS / Modalità background
La regola base delle priorità è: non rilanciare tutto. In Windows ordinario, “sollevare solo i thread importanti e ridurre adeguatamente il lavoro di back-office” funziona meglio. La modalità in background è un meccanismo che tratta non solo la CPU ma anche le risorse come I / O con priorità inferiore.
flowchart TD
Work["Dividi il lavoro"] --> Critical["Thread sensibili alla scadenza"]
Work --> Worker["Memorizza / invia / comprimi / aggrega"]
Work --> UI["UI"]
Critical --> P1["Priorità più alta o MMCSS se necessario"]
Worker --> P2["Modalità background / priorità inferiore"]
UI --> P3["Priorità normale"]
P1 --> Warn["Non iniziare con REALTIME_PRIORITY_CLASS"]
Lista di controllo
- Non tutti i thread hanno priorità alta
- Vengono sollevati solo i thread veramente critici in termini di tempo
- Le attività di back-office come l’archiviazione, l’invio, la compressione e la sincronizzazione vengono spostate in modalità background
- MMCSS viene considerato per l’elaborazione continua del buffer come audio, video, acquisizione e riproduzione
- Pensare prima al thread, prima all’intero processo
REALTIME_PRIORITY_CLASSnon viene utilizzato finché la necessità non è chiaramente stabilita
MMCSS (Multimedia Class Scheduler Service) è particolarmente efficace per l’elaborazione che deve “riempire un buffer entro un tempo fisso”, come audio / video. Si allinea al design di Windows meglio che semplicemente girare sempre un thread ad alta priorità.
Il codice assomiglia più o meno a questo.
DWORD taskIndex = 0;
HANDLE avrt = AvSetMmThreadCharacteristicsW(L"Pro Audio", &taskIndex);
if (!avrt)
{
throw std::runtime_error("AvSetMmThreadCharacteristicsW failed");
}
// Run the time-sensitive loop
if (!AvRevertMmThreadCharacteristics(avrt))
{
throw std::runtime_error("AvRevertMmThreadCharacteristics failed");
}
4.4. Memoria / GC / Costi di prima esecuzione
Se utilizzi new / malloc / List<T>.Add / concatenazione di stringhe / LINQ a ogni passaggio attraverso il percorso attivo, prima o poi emergeranno i costi di raccolta e riposizionamento. GC (garbage collection) in sé non è il cattivo, ma se scrivi codice con un’allocazione pesante, il suo impatto emergerà come jitter.
flowchart LR
Start["Avvio"] --> Alloc["Assegnare i buffer necessari"]
Alloc --> Touch["Toccateli una volta per riscaldare le pagine"]
Touch --> Warm["Togli di mezzo il caricamento JIT / DLL / il primo I / O"]
Warm --> Measure["Quindi eseguire la misurazione / operazione reale"]
Lista di controllo
- Nessuna allocazione / deallocazione della memoria per iterazione sul percorso attivo
- I buffer richiesti vengono preallocati all’avvio
- Le pagine vengono riscaldate toccandole una volta all’avvio
- Il primo JIT, il primo DLL carico e il primo I / O non vengono mescolati nella misurazione reale
- Nessuna struttura enorme o tronchi di lunghezza variabile che crescono all’interno del circuito
- Se
VirtualLockviene utilizzato, è limitato a una regione critica molto piccola
.NETcontrolli lato
- La misurazione del tempo utilizza
Stopwatch/Stopwatch.GetTimestamp() - Nessuna LINQ, concatenazione di stringhe,
ToString()o generazione di log di grandi dimensioni sul percorso attivo async / awaitnon viene portato nel percorso caldo- Il preriscaldamento e il postriscaldamento vengono valutati separatamente
4.5. Impostazioni di alimentazione / EcoQoS / Risoluzione timer
Questa parte è poco affascinante ma efficace. Per quanto rigoroso sia il tuo codice, i risultati non si stabilizzeranno se il controllo di potenza di livello superiore agisce con forza.
flowchart TD
Power["Gestione energetica su Windows ordinario"] --> AC["Funziona con alimentazione CA"]
Power --> Mode["Modalità di alimentazione: tendente alle migliori prestazioni"]
Power --> Plan["Un piano energetico di produzione dedicato, se necessario"]
Power --> QoS["Tieni i processi urgenti lontano da EcoQoS"]
Power --> Timer["Controlla come vengono gestite le richieste di risoluzione del timer"]
Lista di controllo
- La valutazione della produzione viene eseguita prima sull’alimentazione CA
Settings > System > Power & battery > Power modeè impostato su Prestazioni migliori- Le modalità di risparmio batteria / risparmio energetico non sono attive durante le corse
- Sono state controllate le modalità silenziosa / eco / batteria-prima delle utenze specifiche del fornitore
- I processi urgenti non vengono collocati incautamente nel EcoQoS (orientamento all’efficienza energetica QoS)
IGNORE_TIMER_RESOLUTIONnon è abilitato nel processo sensibile al fattore tempo- Verificato se le richieste di risoluzione del timer perdono effetto quando ridotte a icona / nascoste
- Le impostazioni di potenza per l’uso quotidiano e per produzione / misurazione / demo vengono mantenute separate
timeBeginPeriod è utile se utilizzato in modo organizzato, ma non è una panacea.
- Chiamalo appena prima che sia necessario
- Al termine, ripristina con
timeEndPeriod - Dalla versione Windows 10 2004 in poi, non ha più il comportamento completamente globale del passato
- Su Windows 11, un processo con finestre completamente nascoste / minimizzate / non visibili / non udibili potrebbe non essere garantita ad alta risoluzione
- Aumentando la risoluzione non aumenta la precisione di QPC
Se si sospettano effetti di potenza o QoS, controllare lo stato di limitazione della potenza con 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 (performance-leaning) + honor timer-resolution requests
if (!SetProcessInformation(
GetCurrentProcess(),
ProcessPowerThrottling,
&state,
sizeof(state)))
{
throw std::runtime_error("SetProcessInformation failed");
}
ControlMask indica “quali meccanismi vogliamo controllare”, StateMask indica “accendere o spegnere ciascun meccanismo”. Nell’esempio sopra, abbiamo scelto di controllare entrambi i meccanismi e li abbiamo impostati entrambi su off. In altre parole, dichiariamo di non voler cadere in EcoQoS (più vicino a HighQoS) e di non voler che il sistema ignori le richieste di risoluzione del timer. Al contrario, impostando ControlMask su 0, si ritorna al comportamento predefinito lasciato al sistema operativo.
Per chiamarlo da C#
Se vuoi fare la stessa cosa in C#, la dichiarazione P/Invoke è la seguente.
// C# / .NET 8
using System.Runtime.InteropServices;
internal static class PowerQos
{
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_POWER_THROTTLING_STATE
{
public uint Version;
public uint ControlMask;
public uint StateMask;
}
private const uint PROCESS_POWER_THROTTLING_CURRENT_VERSION = 1;
private const uint PROCESS_POWER_THROTTLING_EXECUTION_SPEED = 0x1;
private const uint PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION = 0x4;
// PROCESS_INFORMATION_CLASS 5 (0-based index 4) is ProcessPowerThrottling
private const int ProcessPowerThrottling = 4;
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetProcessInformation(
IntPtr hProcess,
int processInformationClass,
ref PROCESS_POWER_THROTTLING_STATE processInformation,
uint processInformationSize);
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
/// <summary>Disabilita sia l'uso a favore del risparmio energetico che l'ignoranza delle richieste di risoluzione del timer.</summary>
public static void OptOutOfPowerThrottling()
{
var state = new PROCESS_POWER_THROTTLING_STATE
{
Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION,
ControlMask =
PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION,
StateMask = 0,
};
if (!SetProcessInformation(
GetCurrentProcess(),
ProcessPowerThrottling,
ref state,
(uint)Marshal.SizeOf<PROCESS_POWER_THROTTLING_STATE>()))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
}
}
Chiama PowerQos.OptOutOfPowerThrottling(); una volta prima di iniziare il loop sensibile al tempo. L’handle di processo richiede l’accesso PROCESS_SET_INFORMATION, ma con lo pseudo-handle restituito da GetCurrentProcess() per il proprio processo non ci sono problemi.
4.6. Posizionamento CPU / Migrazione core / Riscaldamento
Per il posizionamento della CPU, piuttosto che passare direttamente al blocco su core specifici (affinità hard / blocco CPU), di solito è meglio iniziare con qualcosa di più vicino all’affinità soft: “per favore esegui principalmente su questi core.”
flowchart LR
Measure["Misura prima"] --> Ideal["SetThreadIdealProcessor / CPU Sets"]
Ideal --> Check{"Abbastanza migliorato?"}
Check -->|Yes| Keep["Fermati lì"]
Check -->|No| Hard["Considera SetThreadAffinityMask per ultimo"]
Measure --> Therm["Controlla anche temperatura / orologi / lunghe percorrenze"]
Lista di controllo
- Il posizionamento della CPU viene toccato solo dopo la misurazione
- Non eseguire subito il blocco su core specifici
SetThreadIdealProcessoro CPU Sets provato per primoSetThreadAffinityMasktrattata come ultima risorsa- Temperatura, orologi e limitazione termica controllati su lunghi periodi
- Modalità silenziosa / a basso rumore del laptop selezionata
Come ordine di operazioni, questo flusso è sicuro.
- Misurare prima
- Se necessario, processore ideale / CPU Sets
- Se sono ancora necessari miglioramenti, eseguire il pin su nuclei specifici
Sembra che il blocco su nuclei specifici dovrebbe aiutare, ma rimuove le vie di fuga dell’OS, quindi usato casualmente può effettivamente rendere le cose meno flessibili.
4.7. Isolamento dei conducenti / DPC / ISR / Disturbi esterni
Quando “esplode solo il massimo occasionalmente” o “la media è buona ma p99.9 è pessima”, vale la pena sospettare disturbi esterni oltre il proprio codice.
flowchart TD
Spike["Si è verificato un picco in ritardo / mancato / massimo"] --> Q1{"Anche il tempo di elaborazione è lungo?"}
Q1 -->|Yes| App["Accorciare il percorso attivo / ridurre l'allocazione / rimuovere I / O"]
Q1 -->|No| Q2{"Ci sono picchi DPC / ISR?"}
Q2 -->|Yes| Driver["Controlla USB / Wi-Fi / Bluetooth / GPU / audio / storage / ACPI / aggiornamenti driver"]
Q2 -->|No| Q3{"Errori di pagina / GC / costi di prima esecuzione?"}
Q3 -->|Yes| Mem["Preassegnare / riscaldare / ridurre la pressione heap"]
Q3 -->|No| Q4{"Batteria / risparmio energetico / effetti termici?"}
Q4 -->|Yes| Pow["Alimentazione CA / impostazioni di alimentazione / raffreddamento / test di lunga durata"]
Q4 -->|No| ETW["Scava più a fondo con ETW / WPA / LatencyMon"]
Lista di controllo
- I driver attorno a Wi-Fi / Bluetooth / USB / storage / GPU / audio sono stati controllati
- Rispetto alla sincronizzazione cloud non necessaria, all’indicizzazione e agli aggiornamenti automatici interrotti
- Testato anche se le cose peggiorano quando ridotte a icona o con il display spento
- DPC / ISR trend ispezionati con LatencyMon o ETW
- “La mia elaborazione è pesante” vs “Sono bloccato dall’esterno” esaminati separatamente
5. Misurazione e valutazione
5.1. Cosa registrare
Come minimo, vuoi catturarli.
- Periodo di tempo programmato
- Ora di inizio effettiva
- Orario di fine effettivo
- Ritardo (quanto ritardo è stato l’inizio rispetto al programma)
- Tempo di esecuzione
- Conteggio scadenze non rispettate
- Conteggio scadenze mancate consecutive
- Profondità della coda
- Conteggio delle gocce
- Utilizzo della CPU
- Disallineamento per core
- DPC / ISR punte
- Difetti di pagina
- Variazione temperatura / orologio
Guardare solo le medie rende difficile coglierne l’essenza. Ciò che danneggia la produzione è l’occasionale picco di latenza.
5.2. Come leggere p99 / p99.9 / max
Esistono metriche come p99 per osservare la coda lenta. Le medie da sole nascondono occasionalmente un grande ritardo.
| Metrico | Significato | Intuizione oltre 10.000 misurazioni |
|---|---|---|
| Nella media | Il valore complessivo livellato | Le punte vengono sepolte facilmente |
| p50 | Il valore medio | Vicino all’atmosfera quotidiana |
| p95 | La soglia in cui inizia a mostrare il 5% più lento | Il confine escludendo i 500 più lenti. |
| p99 | La soglia in cui l’1% più lento inizia a mostrare | Il confine escludendo i 100 più lenti |
| p99.9 | La soglia in cui lo 0,1% più lento inizia a mostrare | Il confine escludendo i 10 più lenti |
| massimo | Il caso peggiore | La corsa più lenta |
Ad esempio, con:
- Media: 0,8 ms
- p99: 1,2 ms
- p99.9: 3,5 ms
- massimo: 28 ms
la storia è: di solito veloce, ma con occasionali picchi di grandi dimensioni. Nel Windows ordinario, i veri problemi risiedono quasi sempre in questa coda da p99 a max.
Questo articolo non mostra numeri come prova degli effetti delle configurazioni consigliate. Ritardo e jitter cambiano facilmente con CPU, driver, software residente, impostazioni energetiche e modo di caricare il sistema, quindi i numeri presi da un altro ambiente non possono essere la base per il proprio. Invece, riportiamo la procedura per costruire la stessa tabella nel proprio ambiente.
Procedura minima per calcolare p99 nel proprio ambiente
- Nel percorso critico registra solo. Con
Stopwatch.GetTimestamp()(in C++QueryPerformanceCounter) prendilatenesse tempo di esecuzione e scrivili in un array preallocato. Non calcolare medie o ordinamenti qui - Dopo aver fermato la misura, aggrega: ordina e leggi il valore alla posizione percentile
- Ripeti cambiando condizioni: prima/dopo il warm-up, AC/batteria, UI in primo piano/minimizzata, con/senza carico di altri processi (vedi 5.4)
- Ogni volta che aggiungi una modifica, ripeti la misura nelle stesse condizioni e confronta
// C# / .NET 8. L'aggregazione avviene dopo la misura.
using System.Diagnostics;
// Nel percorso critico scrivi solo nell'array (nessuna allocazione)
long[] latenessTicks = new long[100_000];
int count = 0;
// Esempio: all'interno del ciclo periodico
// latenessTicks[count++] = Stopwatch.GetTimestamp() - scheduledTimestamp;
static double PercentileMs(long[] ticks, int count, double percentile)
{
long[] sorted = ticks.AsSpan(0, count).ToArray();
Array.Sort(sorted);
int index = (int)Math.Ceiling(percentile / 100.0 * count) - 1;
index = Math.Clamp(index, 0, count - 1);
return sorted[index] * 1000.0 / Stopwatch.Frequency;
}
// Uso
// Console.WriteLine($"p50={PercentileMs(latenessTicks, count, 50):F3}ms");
// Console.WriteLine($"p99={PercentileMs(latenessTicks, count, 99):F3}ms");
// Console.WriteLine($"p99.9={PercentileMs(latenessTicks, count, 99.9):F3}ms");
// Console.WriteLine($"max={PercentileMs(latenessTicks, count, 100):F3}ms");
Stopwatch.Frequency è il numero di tick al secondo, quindi dividendo per essa e moltiplicando per 1000 si ottiene il tempo in millisecondi. Con pochi campioni p99.9 non ha senso: per parlarne seriamente servono almeno 10.000 campioni, meglio 100.000.
5.3. Con cosa misurare
Il toolkit è abbastanza standard.
- Misurazione in-app
Per prima cosa cattura
period / lateness / execution time / queue depth / dropte stesso - ETW / WPR / WPA Analizza CPU, cambi di contesto, DPC / ISR, errori di pagina
- LatencyMon Ottieni un rilevamento delle oscillazioni indotte dal conducente
- Monitoraggio temperatura / orologio Attenzione agli effetti termici
flowchart LR
App["Misurazione nell'app"] --> Dist["p50 / p95 / p99 / p99,9 /max"]
App --> Miss["mancati / cadute / profondità della coda"]
ETW["ETW / WPR / WPA"] --> Root["interruttori di contesto / DPC / ISR / errori di pagina"]
Temp["Monitoraggio della temperatura / orologio"] --> Root
Dist --> Decide["Dare priorità ai miglioramenti"]
Miss --> Decide
Root --> Decide
Arrivare fino al WPA richiede un certo sforzo, ma è molto efficace per separare se DPC / ISR sono la causa o se la tua elaborazione è semplicemente pesante.
Come ottenerli e come usarli al minimo
| Strumento | Come ottenerlo | Flusso minimo |
|---|---|---|
| WPR / WPA (Windows Performance Toolkit) | Selezionare “Windows Performance Toolkit” durante l’installazione di Windows ADK (Windows Assessment and Deployment Kit). Il percorso predefinito è C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit |
Aprire un prompt dei comandi come amministratore, (1) avviare la registrazione con wpr -start CPU, (2) far girare il processo problematico per qualche decina di secondi, (3) fermare con wpr -stop trace.etl "Indagine sulla latenza periodica". Aprire trace.etl con WPA. L’elenco dei profili disponibili si ottiene con wpr -profiles |
| LatencyMon | Scaricarlo dal sito di Resplendence Software. L’edizione Home per uso personale è gratuita | Avvia la misurazione e lascia girare il processo target per alcuni minuti. Vengono raccolti il massimo ritardo del timer del kernel e, per ogni driver, tempi di esecuzione di ISR / DPC e hard page fault. Prendi nota del driver che si distacca. |
La prima cosa da guardare in WPA è il grafico CPU relativo a tempi di esecuzione di DPC / ISR e context switch. Qui vedrai “quale driver teneva la CPU nei momenti in cui il tuo thread non riusciva a girare”; incrocia questi istanti con i timestamp dei ritardi registrati in 5.1.
Non ci sono screenshot in questo articolo. Poiché l’interfaccia cambia rapidamente tra le versioni, segui i passaggi sopra e verifica sulla schermata reale.
5.4. Disciplina di prova
Un ambiente da banco silenzioso da solo non è sufficiente per i test. Come minimo, vuoi esaminare queste condizioni separatamente.
- Subito dopo l’avvio, prima del riscaldamento
- Dopo il riscaldamento
- Lunghe tirature continue
- UI in primo piano
- UI ridotta a icona / quasi nascosta
- Alimentazione CA
- Alimentazione a batteria
- Con carico sulla rete o sul disco
Valutare solo al banco rende facile non notare i problemi che si presentano durante il funzionamento reale. Windows Il comportamento ordinario viene facilmente aggirato dal “come viene utilizzata la macchina”, quindi è importante verificarlo in condizioni vicine all’uso effettivo.
6. Criteri pratici per decidere
La tabella rapida per intervallo periodico è stata anticipata nella 1.1. Qui completiamo le due scelte che quella tabella da sola non riesce a sciogliere.
6.1. Quando giudicare “con Windows ordinario non basta”
Quando serve stare sotto 1 ms per periodi lunghi e con carico elevato, spesso è più veloce decidere di spostare fuori la parte critica dal punto di vista temporale anziché continuare a ottimizzare. Le destinazioni tipiche sono firmware lato dispositivo, controller dedicato, FPGA o RTOS. Non si tratta di sensazioni: il criterio sono i numeri del capitolo 5.
- Il percorso caldo non può essere accorciato oltre, ma p99.9 e max continuano a superare il requisito
- I disturbi (DPC / ISR, driver, altri processi) sono la causa e le contromisure lato app non bastano (verifica con 4.7)
- Il requisito non è più “una rara violazione non rompe nulla”, ma “nessuna violazione è tollerata”
6.2. Quando far coesistere GUI / logging / comunicazione / DB
GUI, log, comunicazione e DB dentro lo stesso processo e lo stesso loop fanno sì che le esigenze di una fase a valle facciano saltare le scadenze di una fase a monte. L’attesa di flush del file, la riconnessione del DB o il ridisegno della UI possono allungarsi di decine di millisecondi. La separazione fast path / slow path di 4.2, estesa fino al confine del processo, è un’opzione valida: se la fase a valle si blocca, la fase a monte continua a rispettare i periodi.
7. Conclusione
Ci sono due premesse a cui vale la pena tenersi strette.
- L’obiettivo a cui puntare nel Windows ordinario non è una garanzia del hard real-time, ma una configurazione del soft real-time: latenza e jitter ridotti e nessuna interruzione in caso di scadenza non rispettata
- La vittoria più grande è riordinare il percorso caldo, più che modificare le priorità
Dal punto di vista dell’implementazione, questi sono i risultati:
- Separare il percorso veloce e quello lento
- Utilizza code a lunghezza fissa e decidi in anticipo la politica di overflow
- Misurare con QPC; attendere con eventi / timer di attesa
- Evitare l’allocazione, il blocco dell’I / O e i blocchi pesanti sul percorso attivo
Dal punto di vista operativo, questi sono i vantaggi:
- Funziona con alimentazione CA
- Mantenere una configurazione di alimentazione separata per la produzione
- Ridurre il carico di elaborazione in background non necessario
- Valuta con p99 / p99.9 / max e conteggi mancati
Soft real-time su Windows l’ordinario non è deciso solo dalle impostazioni di priorità: se si lavora su progettazione, implementazione, impostazioni di potenza, misurazione e operazioni come questioni separate, è possibile costruire un sistema straordinariamente stabile.
8. Riferimenti
- Multimedia Class Scheduler Service
- funzione AvSetMmThreadCharacteristicsW
- funzione SetThreadPriority
- funzione SetPriorityClass
- funzione timeBeginPeriod
- funzione CreateWaitableTimerExW
- Acquisizione di timestamp ad alta risoluzione
- funzione QueryPerformanceCounter
- funzione GetSystemTimePreciseAsFileTime
- funzione SetProcessInformation
- funzione VirtualLock
- CPU Sets
- funzione SetThreadIdealProcessor
- funzione SetThreadAffinityMask
- Opzioni di gestione energetica del processore
- Modifica la modalità di alimentazione per il tuo Windows PC
- Impostazioni di alimentazione in Windows 11
- Analisi CPU (WPA / WPT)
- Opzioni della riga di comando di WPR
- Scaricare e installare Windows ADK
- Quality of Service (EcoQoS / HighQoS)
- Struttura PROCESS_POWER_THROTTLING_STATE
- LatencyMon - Resplendence Software
Articoli correlati
Articoli recenti con gli stessi tag per approfondire argomenti vicini.
Quando non conviene portare un'app Windows sul Web: tabella decisionale e la soluzione realistica della "divisione"
Le richieste di trasformare in Web le applicazioni Windows aziendali sono in aumento, ma per le app che gestiscono integrazione con dispo...
Perché preferire l'attesa su evento a Sleep(1) su Windows
Su Windows, la precisione delle attese temporizzate brevi è limitata dalla granularità del clock di sistema e dallo scheduling. Se si sta...
Una lista di controllo minima sulla sicurezza per lo sviluppo di app Windows
Una guida in stile elenco di controllo alle nozioni di base sulla sicurezza per le app aziendali WPF / WinForms / WinUI / C++ / C#: privi...
Perché utilizzare .NET Generic Host e BackgroundService nelle app desktop
Come utilizzare Generic Host e BackgroundService per organizzare l'avvio, l'elaborazione periodica, l'arresto, la registrazione, la confi...
Una guida pratica a FileSystemWatcher: gestire gli eventi persi e duplicati
Organizziamo come utilizzare FileSystemWatcher e le sue insidie: eventi persi, notifiche duplicate, trappole di rilevamento del completam...
Argomenti correlati
Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.
Argomenti tecnici Windows
Portale su sviluppo Windows, analisi dei problemi e valorizzazione delle risorse esistenti.
Domande frequenti
Domande che ricorrono nelle consulenze sull’argomento dell’articolo.
- Windows può eseguire l'elaborazione in tempo reale?
- Windows non è un hard real-time OS, quindi garantire zero scadenze mancate solo con un'app in modalità utente non è realistico. Ciò che puoi ottenere su Windows 10 / 11 ordinario è soft real-time: una configurazione che mantiene bassa la latenza nel caso normale, mantiene piccolo il jitter, non si interrompe quando una scadenza viene occasionalmente mancata e può osservare che l'errore è avvenuto. L'elaborazione periodica da pochi millisecondi a decine di millisecondi, l'audio / video guidato da buffer e i cicli di acquisizione del sensore sono tutti veramente pratici.
- Cosa causa latenza e jitter nell'elaborazione periodica su Windows?
- Le cause principali sono le priorità dello scheduler e dei thread, DPC e ISR di lunga durata da driver come USB, Wi-Fi, storage e GPU, errori di pagina sul percorso caldo, risoluzione del timer e gestione energetica, migrazione del core e limitazione termica. In particolare, anche quando il codice dell'applicazione funziona correttamente, le circostanze del driver o dell'hardware possono bloccare i thread in modalità utente, quindi il semplice aumento della priorità dell'app in genere non risolve il problema.
- Perché Sleep(1) è dannoso per un ciclo periodico di 1 ms?
- Sleep(1) non produce un periodo di 1 ms: attende circa 1 ms o più e quindi aggiunge sopra il tempo di esecuzione del lavoro e il superamento dell'attesa si accumula come deriva. Un ciclo periodico è più stabile se guidato da scadenze assolute: aggiorna la scadenza con `next += period`, attendi fino a poco prima (`next - margin`) con un evento del dispositivo o un timer di attesa ad alta risoluzione e utilizza solo una rotazione molto breve per la regolazione fine finale. Il tempo deve essere misurato con QueryPerformanceCounter o Stopwatch in .NET.
- Come devo valutare se il mio sistema soft real-time è stabile?
- Non fare affidamento sulle medie: ciò che danneggia la produzione è l'occasionale picco di latenza, che le medie nascondono. Registra gli orari di inizio e fine programmati rispetto a quelli effettivi e valuta la coda lenta con p99, p99.9 e max, insieme al conteggio delle scadenze mancate, alla profondità della coda e al conteggio delle cadute. Per l'analisi delle cause principali, utilizza ETW / WPA e LatencyMon per separare se DPC e ISR ti stanno bloccando o se la tua elaborazione è semplicemente pesante e testa in condizioni realistiche, tra cui alimentazione a batteria, finestre ridotte a icona e lunghi cicli continui.
Profilo dell’autore
Pagina di presentazione dell’autore dell’articolo.
Go Komura
Rappresentante di KomuraSoft LLC
Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.