Guía práctica para lograr el máximo de soft real-time posible en un Windows convencional
· Actualizado el: · Go Komura · Desarrollo en Windows, Soft real-time, Diseño, Medición
Cuando se desarrollan en Windows procesos como el procesamiento periódico, el audio, el video, la medición o el control de equipos —es decir, procesos donde «un retraso es un problema»—, suele darse la impresión de que «en Windows va a ser complicado». Esa impresión es en parte acertada y en parte no: Windows no es un sistema operativo hard real-time, pero si se cuidan bien el diseño, la implementación, la medición y la operación, se puede llegar a un estado bastante práctico como soft real-time.
En este artículo se trata un Windows 10/11 convencional, sin dar por supuesto ninguna extensión RTOS especial, ningún controlador de kernel propio ni ningún controlador dedicado. El tema es más bien práctico: hasta dónde se pueden reducir la latencia y el jitter en una aplicación en modo usuario sobre el PC de escritorio o portátil habitual. Aunque los detalles difieren entre audio, video, control periódico y adquisición de datos, los puntos que suelen causar problemas son bastante comunes, así que aquí se reúne esa parte común en forma de lista de verificación.
Público objetivo y el idioma de los ejemplos de código
Este artículo está escrito para quienes desarrollan en Windows procesos donde «un retraso es un problema» (control periódico, audio y video, medición, control de equipos). Se asume el desarrollo de aplicaciones en modo usuario; la implementación de controladores en modo kernel queda fuera de alcance.
El idioma de los ejemplos de código se divide de la siguiente manera.
| Contenido | Lenguaje | Ubicación |
|---|---|---|
| Partes que llaman directamente a la API Win32, como el bucle periódico, MMCSS o el QoS de energía | C++ (Win32) | 4.1, 4.3, 4.5 |
| La misma API Win32 invocada desde C# | C# (P/Invoke) | 4.5 |
| Medición de tiempo, GC y precauciones sobre la asignación de memoria | .NET (C#) | «Comprobaciones del lado de .NET» en 4.4, 5.2 |
La explicación de las causas hasta el capítulo 3 y el cuerpo de la lista de verificación del capítulo 4 no dependen del lenguaje. Quienes solo usen C# pueden leer el código en C++ simplemente como una explicación de «qué API se llama y en qué orden».
Índice
- Primero, la conclusión (en una frase)
- 1.1. Tabla rápida por rango de periodo (por dónde empezar a leer)
- Qué es el «soft real-time» en un Windows convencional
- 2.1. Qué entendemos aquí por «Windows convencional»
- 2.2. Qué se puede hacer y a partir de dónde se complica
- 2.3. Antes que nada, un vistazo a los términos
- Principales causas de latencia y jitter
- 3.1. El planificador y las prioridades
- 3.2. DPC/ISR y los controladores
- 3.3. Fallos de página y memoria
- 3.4. Resolución del temporizador y gestión de energía
- 3.5. Migración entre núcleos y calor
- Lista de verificación práctica para reducir retrasos en un Windows convencional
- 4.1. El bucle periódico y el método de espera
- 4.2. Fast path, slow path y la cola de longitud fija
- 4.3. Prioridad, MMCSS y background mode
- 4.4. Memoria, GC y el coste de la primera ejecución
- 4.5. Configuración de energía, EcoQoS y la resolución del temporizador
- 4.6. Ubicación de CPU, migración entre núcleos y calor
- 4.7. Aislar controladores, DPC/ISR y otras perturbaciones
- Medición y evaluación
- 5.1. Qué se debe registrar
- 5.2. Cómo leer p99, p99.9 y max
- 5.3. Con qué herramientas mirar
- 5.4. Buenas prácticas para las pruebas
- Criterios generales para decidir
- Resumen
- Referencias
1. Primero, la conclusión (en una frase)
- En un Windows convencional, el objetivo no es garantizar hard real-time, sino lograr, como soft real-time, una configuración que «se retrase poco y no se rompa aunque se retrase».
- Lo que más impacto tiene es hacer el hot path corto, de longitud fija y no bloqueante.
- Se separan el fast path (adquisición / control) y el slow path (guardado / comunicación / interfaz), conectados por una cola de longitud fija.
- El bucle periódico no se deja en manos de
Sleep(1), sino que gira con un plazo absoluto. - En flujos continuos como el audio o el video, lo primero que hay que considerar es MMCSS.
- Para medir el tiempo se usa QueryPerformanceCounter (QPC), o
Stopwatchen .NET. - Para esperar, se da preferencia a un evento de dispositivo o a un waitable timer de alta precisión (temporizador esperable).
timeBeginPeriodse usa solo mientras haga falta. No se diseña asumiendo que estará activo todo el tiempo.- En el uso real ayudan la alimentación de CA, el modo de energía, el tratamiento de EcoQoS y ordenar la carga en segundo plano.
- La evaluación no se basa solo en el promedio, sino en p99 (el límite a partir del cual, en 100 mediciones, empieza a verse 1 caso lento), p99.9, max, el número de miss, DPC/ISR, page fault y la profundidad de la cola.
En resumen, en un Windows convencional es más eficaz reducir mediante el diseño las causas del retraso que subir la prioridad. La prioridad y la configuración de energía son importantes, pero por sí solas no bastan para lograr estabilidad.
1.1. Tabla rápida por rango de periodo (por dónde empezar a leer)
Como es un artículo largo, se coloca primero esta tabla rápida para que pueda leer solo la fila que corresponde a su caso. Este contenido, que antes estaba al final (capítulo 6), se ha adelantado aquí.
| Periodo / requisito | Configuración inicial | Secciones clave |
|---|---|---|
| Del orden de 10-20 ms, con capacidad de absorber alguna variación ocasional | Separar fast path y slow path, cola de longitud fija, prioridad normal a algo alta, orientado a eventos. Con esto suele bastar | 4.1, 4.2 |
| Del orden de 1-5 ms, con necesidad de cumplir el plazo de forma continua | Además de lo anterior, hot path sin asignaciones, hilo dedicado, MMCSS o un ajuste cuidadoso de prioridad, waitable timer de alta precisión, y revisar la alimentación de CA y la configuración de energía | De 4.1 a 4.5 |
| Cerca de menos de 1 ms, sin margen para fallar ni en funcionamiento prolongado ni con carga alta | Muy difícil de lograr solo con modo usuario en un Windows convencional. Conviene estudiar primero sacar la parte crítica a otro lugar (firmware del propio dispositivo, controlador dedicado, FPGA, RTOS) | 2.2, 6 |
| Quiere hacerlo convivir todo con GUI / registro / comunicación / BD | No cargarlo todo en «un proceso, un bucle»: separar responsabilidades. Las necesidades de las etapas posteriores tienden a romper el plazo de las anteriores | 4.2, 4.3, 6 |
El aislamiento de causas y las buenas prácticas de medición son comunes a cualquier rango (capítulos 3 y 5).
2. Qué es el «soft real-time» en un Windows convencional
2.1. Qué entendemos aquí por «Windows convencional»
Lo que aquí llamamos Windows convencional parte, a grandes rasgos, de los siguientes supuestos.
- Un PC de escritorio o portátil habitual con Windows 10/11
- Sin extensiones RTOS propias
- Sin desarrollo de controladores en modo kernel propios
- Una aplicación normal en modo usuario
- Ajustes realizados con la API y la configuración habituales de Windows
En otras palabras, no se trata de «construir un equipo dedicado entero para control en tiempo real», sino de «hasta dónde se puede llegar de forma realista sobre un PC Windows convencional».
flowchart LR
accTitle: Objetivo del soft real-time en un Windows convencional
accDescr: Diagrama que muestra cómo una app en modo usuario sobre un PC Windows 10/11 convencional persigue el soft real-time reduciendo la latencia, el jitter y observando los deadline miss sin romperse, frente a la vía alternativa de garantizar cero incumplimientos de plazo mediante RTOS, controladores dedicados, FPGA o procesamiento en el propio dispositivo.
A["PC Windows 10 / 11 convencional"] --> B["Aplicación en modo usuario"]
B --> C["Persigue el soft real-time"]
C --> D["Reduce la latencia"]
C --> E["Reduce el jitter"]
C --> F["Observa los deadline miss para no romperse"]
G["Quiere garantizar cero incumplimientos de plazo"] -.-> H["RTOS / controlador dedicado / FPGA / procesamiento en el dispositivo"]
2.2. Qué se puede hacer y a partir de dónde se complica
Incluso en un Windows convencional, procesos como los siguientes permiten lograr, de forma bastante realista, un estado en el que «cuesta que se retrasen».
- Procesamiento periódico de pocos a varias decenas de milisegundos
- Manejo de audio/video por búfer
- Adquisición de sensores y bucles de control
- Procesamiento a periodo constante al estilo de un PLC por software
- Canalizaciones de baja latencia que corren en un hilo separado de la interfaz de usuario
Sin embargo, ese «se puede» no significa que se puedan eliminar por completo los picos de retraso ocasionales; lo que en realidad se persigue es este otro estado.
- Mantener baja la latencia en condiciones normales
- Reducir el jitter
- No romperse aunque ocasionalmente se incumpla el plazo
- Poder observar el hecho de que se incumplió
Por el contrario, cuando el requisito se convierte en lo siguiente, cumplirlo únicamente con modo usuario en un Windows convencional resulta bastante difícil.
- Garantizar cero incumplimientos de plazo
- Mantener de forma estable y prolongada un margen por debajo de cientos de microsegundos
- Convivir con una GUI pesada, red y almacenamiento
- Hacerlo funcionando con batería o priorizando el ahorro de energía
- No admitir tampoco picos originados por controladores o dispositivos
En estos casos es más seguro considerar trasladar únicamente la parte realmente crítica en tiempo al firmware del propio dispositivo, a un controlador dedicado, a un FPGA o a un RTOS.
2.3. Antes que nada, un vistazo a los términos
Antes de continuar, conviene tener claro el significado de los términos que aparecen en este artículo.
| Término | En una frase | Cómo se usa en la práctica |
|---|---|---|
| soft real-time | Puede haber algún retraso ocasional, pero la idea es reducirlo y no romperse aunque ocurra | Es lo primero que se busca en un Windows convencional |
| hard real-time | El mundo donde se garantiza cero incumplimientos de plazo | No es un objetivo alcanzable solo con modo usuario en un Windows convencional |
| jitter | Variación del periodo o del tiempo de respuesta | Aunque el promedio sea bueno, un jitter grande vuelve inestable el funcionamiento real |
| deadline miss | Que el procesamiento no termine antes de la hora prevista | No ocultarlo: contarlo y registrarlo en el log |
| p99 / p99.9 | Indicadores que muestran la cola de los valores más lentos | p99 es «el límite a partir del cual empieza a verse 1 de cada 100 casos lentos» |
| DPC / ISR | Procesamiento del lado del kernel relacionado con controladores e interrupciones | Si duran mucho, los hilos en modo usuario quedan a la espera |
| MMCSS | Mecanismo de Windows que asigna CPU a procesos sensibles al tiempo, como audio o video | Muy eficaz en procesos que no deben quedarse sin datos en el búfer |
| QPC | Se refiere a QueryPerformanceCounter |
Base para medir tiempo transcurrido: un contador de alta precisión, no un reloj de pared |
| waitable timer (temporizador esperable) | Objeto del kernel que pasa a estado signaled en el momento indicado. Al añadir CREATE_WAITABLE_TIMER_HIGH_RESOLUTION a CreateWaitableTimerExW se obtiene la versión de alta precisión |
Más adecuado que Sleep como base de la espera periódica (4.1) |
| EcoQoS | Estado clasificado como «puede priorizarse el ahorro de energía». Reduce la frecuencia de la CPU o desplaza el trabajo hacia núcleos más eficientes | Evitarlo en procesos sensibles al tiempo. Si no se indica lo contrario, el sistema operativo lo infiere automáticamente (4.5) |
IGNORE_TIMER_RESOLUTION |
Indicación de que puede ignorarse la solicitud de resolución del temporizador del proceso (PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION) |
Si está activo, el efecto de timeBeginPeriod desaparece. En Windows 11 puede aplicarse automáticamente al ocultarse de la pantalla (4.5) |
| CPU Sets | Mecanismo para indicar de forma flexible que un hilo o proceso «se ejecute preferentemente en este grupo de núcleos» | Probarlo antes de fijar núcleos específicos (4.6) |
| ETW / WPR / WPA | La infraestructura estándar de trazas de Windows (ETW) junto con su herramienta de registro (WPR) y su GUI de análisis (WPA) | Se usa para investigar context switch, DPC/ISR y page fault (5.3) |
| LatencyMon | Herramienta de terceros para observar retrasos causados por controladores | Permite ver el tiempo de ejecución de DPC/ISR por controlador y detectar sospechosos (5.3) |
3. Principales causas de latencia y jitter
En un Windows convencional, los motivos por los que se retrasa el procesamiento periódico suelen reducirse a alguno de los que aparecen en este diagrama.
flowchart TD
accTitle: Causas principales de retraso y jitter
accDescr: Diagrama que muestra que un retraso en el procesamiento periódico puede originarse en el planificador y las prioridades, en DPC/ISR y los controladores, en fallos de página y memoria, en la resolución del temporizador y la gestión de energía, o en la migración entre núcleos y el calor.
Late["El procesamiento periódico se retrasa"] --> S["Planificador / prioridades"]
Late --> D["DPC / ISR / controladores"]
Late --> M["Fallos de página / memoria"]
Late --> T["Resolución del temporizador / gestión de energía"]
Late --> C["Migración entre núcleos / calor"]
3.1. El planificador y las prioridades
En Windows, el orden de ejecución de los hilos se determina por la prioridad. Entre hilos de la misma prioridad se rota en round-robin, y en cuanto un hilo de mayor prioridad queda listo para ejecutarse, desplaza a los de menor prioridad.
Es decir, aunque el hilo periódico esté bien escrito,
- Otro hilo
- Otro proceso
- Procesamiento interno del sistema operativo
- Productos de seguridad
- Procesamiento auxiliar de dispositivos
- Sincronización en segundo plano
pueden perfectamente ejecutarse antes.
3.2. DPC/ISR y los controladores
Este punto es bastante importante. Aunque se ajuste la prioridad del lado de la aplicación, si el DPC (Deferred Procedure Call) o el ISR (Interrupt Service Routine) duran mucho, durante ese tiempo los hilos en modo usuario no pueden ejecutarse.
Los dispositivos y controladores que suelen originar esto son, entre otros, los siguientes.
- USB
- Wi-Fi / Bluetooth
- Almacenamiento
- Audio
- GPU
- ACPI y lo relacionado con la energía
Aunque el código de la aplicación no tenga ningún defecto, puede verse detenido por causas del controlador o del hardware. En este punto, pensar que «si subo aún más la prioridad de la aplicación, ganaré» suele terminar mal.
3.3. Fallos de página y memoria
Si en el hot path ocurre un page fault (la página necesaria no está en memoria y hay que ir a buscarla), la latencia se dispara de golpe.
A continuación se listan los patrones que conviene evitar en especial.
- Confirmación (commit) de página en el primer acceso
- Carga diferida (lazy loading)
- Paginación de entrada de archivos mapeados en memoria
- Asignación dinámica más allá de lo necesario
- Objetos grandes o un heap fragmentado
En el cuerpo del procesamiento periódico, lo adecuado es, más o menos, reservar de antemano la memoria necesaria y tocarla una vez al iniciar.
3.4. Resolución del temporizador y gestión de energía
«Quiero que se ejecute cada 1 ms, así que uso Sleep(1)» casi nunca funciona bien.
La precisión de la espera en Windows depende de la resolución del temporizador, la planificación y el estado de energía.
Además, no hay que pasar por alto que la configuración que aumenta la resolución del temporizador mejora algo la precisión de la espera, pero tiene efectos secundarios sobre el consumo de energía y el comportamiento general del sistema.
3.5. Migración entre núcleos y calor
Cuando un hilo migra entre núcleos, la caché tiene que volver a «calentarse». Esto en sí mismo suele estar bien gestionado por el sistema operativo, pero en entornos con carga alta se convierte en una causa de variación.
Además, cuando se funciona durante mucho tiempo, el calor tampoco se puede ignorar. Si entra en juego el thermal throttling, el periodo que hasta entonces era estable puede desmoronarse.
4. Lista de verificación práctica para reducir retrasos en un Windows convencional
A partir de aquí empieza la parte práctica. Frente a las causas vistas en la sección anterior, se resume en forma de lista de verificación qué comprobar, qué evitar y qué decidir de antemano en un Windows convencional.
4.1. El bucle periódico y el método de espera
El antipatrón típico es este.
while (running)
{
Sleep(1);
Step();
}
Esto no es «un periodo de 1 ms», sino un bucle que espera al menos 1 ms, aproximadamente, y le suma encima el tiempo de ejecución de Step().
Y además, el exceso de espera se va acumulando tal cual.
flowchart LR
accTitle: Bucle periódico basado en tiempo relativo frente a plazo absoluto
accDescr: Comparación entre un bucle basado en tiempo relativo, donde la espera fija y el paso de ejecución acumulan poco a poco el error de espera y el tiempo de cómputo, y un bucle basado en plazo absoluto, donde next += period, WaitUntil, un spin corto y FastStep evitan que se acumule la deriva.
subgraph Bad["Basado en tiempo relativo"]
B1["Sleep(1)"] --> B2["Step()"]
B2 --> B1
end
B2 --> B3["El error de espera y el tiempo de ejecución se van acumulando"]
subgraph Good["Basado en plazo absoluto"]
G1["next += period"] --> G2["WaitUntil(next - margin)"]
G2 --> G3["Spin corto si hace falta"]
G3 --> G4["FastStep()"]
G4 --> G1
end
G4 --> G5["Difícilmente acumula deriva"]
Lista de verificación
- No se usa
Sleep(1)como base del bucle periódico - El periodo gira con el plazo absoluto de
next += period - La espera da preferencia a un evento de dispositivo o a un waitable timer (temporizador esperable)
- Solo el ajuste final se limita a un busy-spin (espera activa) muy corto
timeBeginPeriodse usa solo mientras haga falta, revirtiéndolo después- Se comprueba el comportamiento también minimizado, oculto o cuando no es visible
El bucle periódico es más estable si gira con un plazo absoluto en lugar de con tiempo relativo.
int64_t next = QpcNow() + periodTicks;
while (running)
{
WaitUntil(next - wakeMarginTicks);
while (QpcNow() < next)
{
CpuRelax(); // Solo al final, spin corto
}
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 y la cola de longitud fija
La base de la arquitectura es colocar en el fast path solo el «trabajo sensible al plazo» y desplazar todo lo demás al slow path.
flowchart LR
accTitle: Separación entre fast path y slow path con cola de longitud fija
accDescr: Diagrama en el que el evento del dispositivo o de captura entra al fast path de adquisición, control y copia mínima, pasa por una cola de longitud fija hacia el slow path de guardado, envío, interfaz y agregación, mientras el fast path registra lateness, miss y profundidad de cola hacia el slow path.
Input["Dispositivo / evento de captura"] --> Fast["fast path: adquisición, control, copia mínima"]
Fast --> Queue["Cola de longitud fija"]
Queue --> Slow["slow path: guardado, envío, interfaz, agregación"]
Fast --> Metrics["Registra lateness / miss / profundidad de cola"]
Metrics --> Slow
Lo que se hace en el fast path se limita, más o menos, a lo siguiente.
- Adquisición de datos
- Cálculo del valor de control
- Copia mínima necesaria
- Marca de tiempo
- Inserción en la cola
- Registro de miss / overrun
Todo lo demás se deja caer al slow path.
Lista de verificación
- No se escribe a archivo, se envía por red ni se escribe en BD dentro del hot path
- No se hace registro de log pesado,
Flushni RPC síncrono dentro del hot path - El fast path y el slow path están claramente separados por hilo o por responsabilidad
- La cola tiene longitud fija
- Se ha decidido de antemano la política para cuando la cola se desborde
- Se observan el número de miss, el número de drop y la profundidad de la cola
- La actualización de la interfaz y la agregación de logs están separadas hacia un periodo más bajo
Cuando la cola se llena, es más seguro no dejar la política en la ambigüedad.
flowchart TD
accTitle: Política ante el desbordamiento de la cola
accDescr: Diagrama de decisión que, ante una cola llena, pregunta qué se debe proteger, y muestra tres respuestas: descartar los elementos antiguos y conservar el más reciente, emitir una alerta o detener el flujo aguas arriba, o descartar los elementos antiguos y solo registrar el número de descartes.
Overflow["La cola está llena"] --> Policy{"¿Qué se debe proteger?"}
Policy -->|El valor más reciente importa| Latest["Descarta los elementos antiguos y conserva el más reciente"]
Policy -->|Todos los elementos importan| All["Alerta / detención / control aguas arriba"]
Policy -->|Uso para registro| Log["Descarta los elementos antiguos y solo registra el número de descartes"]
4.3. Prioridad, MMCSS y background mode
La base de la prioridad es no subirlo todo. En un Windows convencional funciona mejor «subir solo los hilos importantes y bajar bien el trabajo secundario». El background mode es un mecanismo para tratar con prioridad más baja no solo la CPU, sino también recursos como la E/S.
flowchart TD
accTitle: Reparto de prioridades por tipo de trabajo
accDescr: Diagrama que separa el trabajo en hilos sensibles al plazo, trabajo de guardado, envío, compresión y agregación, e interfaz de usuario, asignando a los primeros una prioridad más alta o MMCSS si hace falta, a los segundos background mode o una prioridad más baja, a la interfaz la prioridad normal, y advirtiendo no usar REALTIME_PRIORITY_CLASS desde el principio.
Work["Reparte el trabajo"] --> Critical["Hilos sensibles al plazo"]
Work --> Worker["Guardado / envío / compresión / agregación"]
Work --> UI["Interfaz de usuario"]
Critical --> P1["Prioridad más alta o MMCSS si hace falta"]
Worker --> P2["background mode / prioridad más baja"]
UI --> P3["Prioridad normal"]
P1 --> Warn["No empezar directamente con REALTIME_PRIORITY_CLASS"]
Lista de verificación
- No se pone a todos los hilos en prioridad alta
- Solo se sube la prioridad de los hilos realmente exigentes en tiempo
- El trabajo secundario (guardado, envío, compresión, sincronización) se deja en background mode
- Se considera MMCSS en el procesamiento continuo por búfer, como audio, video, captura o reproducción
- Se piensa primero por hilo, más que por el proceso completo
- No se usa
REALTIME_PRIORITY_CLASShasta que la necesidad quede clara
MMCSS (Multimedia Class Scheduler Service) es especialmente eficaz en procesos como el audio o el video, donde se quiere «llenar el búfer dentro de un tiempo determinado». Se ajusta mejor al diseño de Windows que simplemente mantener siempre un hilo de alta prioridad en ejecución.
El aspecto del código es más o menos así.
DWORD taskIndex = 0;
HANDLE avrt = AvSetMmThreadCharacteristicsW(L"Pro Audio", &taskIndex);
if (!avrt)
{
throw std::runtime_error("AvSetMmThreadCharacteristicsW failed");
}
// Ejecutar aquí el bucle sensible al tiempo
if (!AvRevertMmThreadCharacteristics(avrt))
{
throw std::runtime_error("AvRevertMmThreadCharacteristics failed");
}
4.4. Memoria, GC y el coste de la primera ejecución
Si en el hot path se usan cada vez new, malloc, List<T>.Add, concatenación de cadenas o LINQ, tarde o temprano saldrán a la superficie los efectos de la recolección o la reubicación. El GC (recolector de basura) en sí no es malo, pero si se escribe código con muchas asignaciones, su efecto se manifiesta como jitter.
flowchart LR
accTitle: Secuencia de calentamiento antes de medir
accDescr: Diagrama que muestra la secuencia desde el arranque hasta reservar los búferes necesarios, tocarlos una vez para calentar las páginas, completar el JIT, la carga de DLL y la E/S inicial, y solo después realizar la medición o el funcionamiento real.
Start["Arranque"] --> Alloc["Reserva los búferes necesarios"]
Alloc --> Touch["Los toca una vez para calentar las páginas"]
Touch --> Warm["Completa el JIT, la carga de DLL y la E/S inicial"]
Warm --> Measure["Después realiza la medición o el funcionamiento real"]
Lista de verificación
- No se hace asignación/liberación de memoria en cada iteración del hot path
- Los búferes necesarios se reservan de antemano al iniciar
- Al iniciar, se tocan una vez para calentar las páginas
- El JIT inicial, la primera carga de DLL y la E/S inicial no se mezclan con la medición real
- No se dejan crecer estructuras enormes ni logs de longitud variable dentro del bucle
- Si se usa
VirtualLock, se limita a una región crítica muy pequeña
Comprobaciones del lado de .NET
- Se usa
Stopwatch/Stopwatch.GetTimestamp()para medir el tiempo - No se usan LINQ, concatenación de cadenas,
ToString()ni generación de logs enormes en el hot path - No se introduce
async/awaiten el hot path - Se evalúa por separado antes y después del calentamiento
4.5. Configuración de energía, EcoQoS y la resolución del temporizador
Este punto es poco vistoso, pero es eficaz. Por mucho que se afine el código, si el control de energía de nivel superior tiene un efecto fuerte, el resultado no será estable.
flowchart TD
accTitle: Gestión de energía en un Windows convencional
accDescr: Diagrama que agrupa bajo la gestión de energía de un Windows convencional la ejecución con alimentación de CA, un modo de energía orientado al máximo rendimiento, un plan de energía dedicado para producción si hace falta, evitar EcoQoS en procesos sensibles al tiempo, y comprobar el tratamiento de las solicitudes de resolución del temporizador.
Power["Gestión de energía en un Windows convencional"] --> AC["Ejecutar con alimentación de CA"]
Power --> Mode["Modo de energía: orientado al máximo rendimiento"]
Power --> Plan["Plan de energía dedicado para producción si hace falta"]
Power --> QoS["Evitar EcoQoS en procesos sensibles al tiempo"]
Power --> Timer["Comprobar el tratamiento de las solicitudes de resolución del temporizador"]
Lista de verificación
- La evaluación de producción se realiza primero con alimentación de CA
[Configuración] > [Sistema] > [Energía y batería] > [Modo de energía]está orientado hacia máximo rendimiento- No se usa el modo de ahorro de batería ni un modo que priorice el ahorro de energía durante la ejecución
- Se comprueban los modos silencioso, eco o de prioridad de batería de utilidades propias del fabricante
- No se pone descuidadamente en EcoQoS (QoS orientado al ahorro de energía) a un proceso sensible al tiempo
IGNORE_TIMER_RESOLUTIONno está activo en el lado del proceso sensible al tiempo- Se comprueba si el efecto de la solicitud de resolución del temporizador cambia al minimizar u ocultar la ventana
- Se separa la configuración de energía de uso habitual de la de producción/medición/demostración
timeBeginPeriod es útil si se usa de forma ordenada, pero no es una solución milagrosa.
- Se llama justo antes de necesitarlo
- Al terminar, se revierte con
timeEndPeriod - Desde Windows 10 versión 2004, ya no tiene el comportamiento global completo de antes
- En Windows 11, si un proceso con ventana queda completamente oculto, minimizado, invisible o inaudible, es posible que no se garantice una resolución alta
- Aumentar la resolución no mejora la precisión de QPC
Si se sospecha del efecto de la energía o del QoS, se puede comprobar el estado del power throttling 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 (orientado al rendimiento) + respeta la solicitud de resolución del temporizador
if (!SetProcessInformation(
GetCurrentProcess(),
ProcessPowerThrottling,
&state,
sizeof(state)))
{
throw std::runtime_error("SetProcessInformation failed");
}
ControlMask indica «qué mecanismos se controlan uno mismo», y StateMask indica «si ese mecanismo se activa o se desactiva». En el ejemplo anterior se eligen dos mecanismos como objeto de control y ambos se desactivan (off). Es decir, se declara no degradar a EcoQoS (orientado a HighQoS) y no dejar que se ignore la solicitud de resolución del temporizador. Por el contrario, si se pone ControlMask en 0, ambos vuelven a quedar en manos del sistema operativo (comportamiento predeterminado).
Cómo llamarlo desde C#
Si se quiere hacer lo mismo desde C#, la declaración P/Invoke queda así.
// 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;
// El quinto valor de PROCESS_INFORMATION_CLASS (4 empezando desde 0) es 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>Anula tanto el tratamiento orientado al ahorro de energía como la ignorancia de la solicitud de resolución del temporizador.</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());
}
}
}
La llamada consiste en ejecutar una sola vez PowerQos.OptOutOfPowerThrottling(); antes de iniciar el bucle sensible al tiempo. El identificador de proceso necesita el permiso de acceso PROCESS_SET_INFORMATION, pero no hay problema si se usa el pseudo-identificador del propio proceso que devuelve GetCurrentProcess().
4.6. Ubicación de CPU, migración entre núcleos y calor
En la ubicación de CPU, suele funcionar mejor empezar por una indicación cercana a la soft affinity («preferentemente que se ejecute en este grupo de núcleos») en lugar de pasar directamente a fijar un núcleo específico (hard affinity / CPU pinning).
flowchart LR
accTitle: Orden recomendado para ajustar la ubicación de CPU
accDescr: Diagrama que empieza por medir, prueba SetThreadIdealProcessor o CPU Sets, comprueba si la mejora es suficiente para detenerse ahí o, en caso contrario, considerar SetThreadAffinityMask como último recurso, y a la vez recomienda comprobar la temperatura, el reloj y el funcionamiento prolongado.
Measure["Medir primero"] --> Ideal["SetThreadIdealProcessor / CPU Sets"]
Ideal --> Check{"¿Mejoró lo suficiente?"}
Check -->|Sí| Keep["Detenerse ahí"]
Check -->|No| Hard["Considerar SetThreadAffinityMask como último recurso"]
Measure --> Therm["Comprobar también temperatura / reloj / funcionamiento prolongado"]
Lista de verificación
- Se mide primero y solo después se ajusta la ubicación de CPU
- No se fija directamente un núcleo específico
- Se prueba primero
SetThreadIdealProcessoro CPU Sets SetThreadAffinityMaskse trata como último recurso- En funcionamiento prolongado se comprueban la temperatura, el reloj y el thermal throttling
- Se comprueban el modo silencioso o de bajo ruido del portátil
En cuanto al orden, este flujo es el más seguro.
- Medir primero
- Si hace falta, ideal processor / CPU Sets
- Si aún así se necesita mejorar, fijar un núcleo específico
Fijar un núcleo específico parece eficaz, pero reduce el margen de maniobra del sistema operativo, así que usarlo a la ligera puede volver el sistema, al contrario, menos flexible.
4.7. Aislar controladores, DPC/ISR y otras perturbaciones
Cuando ocurre que «de vez en cuando solo el max se dispara» o «el promedio es bueno pero el p99.9 es malo», conviene sospechar también de perturbaciones ajenas al código de la aplicación.
flowchart TD
accTitle: Árbol de decisión para diagnosticar picos de retraso
accDescr: Diagrama de decisión que, ante un pico de late, miss o max, pregunta primero si el propio tiempo de procesamiento también es largo, y en ese caso indica acortar el hot path, reducir asignaciones y eliminar E/S, y en caso contrario pregunta por picos de DPC/ISR, que llevan a revisar controladores de USB, Wi-Fi, Bluetooth, GPU, audio, almacenamiento y ACPI, y si no hay picos pregunta por page fault, GC o coste de primera ejecución, que lleva a reservar memoria por adelantado, calentar y reducir la carga del heap, y si tampoco es eso pregunta por el efecto de la batería, el ahorro de energía o el calor, que lleva a usar alimentación de CA, ajustar la energía, enfriar y hacer pruebas largas, y en última instancia recomienda profundizar con ETW, WPA o LatencyMon.
Spike["Aparece un pico de late / miss / max"] --> Q1{"¿El propio tiempo de procesamiento también es largo?"}
Q1 -->|Sí| App["Acortar hot path / reducir asignaciones / eliminar E/S"]
Q1 -->|No| Q2{"¿Hay picos de DPC / ISR?"}
Q2 -->|Sí| Driver["Revisar USB / Wi-Fi / Bluetooth / GPU / audio / almacenamiento / ACPI / actualización de controladores"]
Q2 -->|No| Q3{"¿Hay page fault / GC / coste de primera ejecución?"}
Q3 -->|Sí| Mem["Reserva previa / calentamiento / reducir carga del heap"]
Q3 -->|No| Q4{"¿Hay efecto de batería / ahorro de energía / calor?"}
Q4 -->|Sí| Pow["Alimentación de CA / configuración de energía / enfriamiento / pruebas largas"]
Q4 -->|No| ETW["Profundizar con ETW / WPA / LatencyMon"]
Lista de verificación
- Se revisan los controladores de Wi-Fi, Bluetooth, USB, almacenamiento, GPU y audio
- Se comparan los resultados deteniendo la sincronización en la nube, la indexación y las actualizaciones automáticas innecesarias
- Se prueba también si se desestabiliza al minimizar o al apagar la pantalla
- Se observa la tendencia de DPC/ISR con LatencyMon o ETW
- Se distingue entre si «el propio procesamiento es pesado» o si «algo externo lo está deteniendo»
5. Medición y evaluación
5.1. Qué se debe registrar
Como mínimo, conviene registrar al menos lo siguiente.
- Hora prevista del periodo
- Hora real de inicio
- Hora real de finalización
- lateness (cuánto se retrasó el inicio real respecto al inicio previsto)
- Tiempo de ejecución
- Número de missed deadline
- Número de missed deadline consecutivos
- Profundidad de la cola (queue depth)
- Número de drop
- Uso de CPU
- Desequilibrio entre núcleos
- Picos de DPC/ISR
- page fault
- Variaciones de temperatura y reloj
Mirar solo el promedio dificulta captar lo esencial. Lo que causa problemas en producción son los picos de retraso grandes que aparecen de vez en cuando.
5.2. Cómo leer p99, p99.9 y max
Indicadores como p99 sirven para observar la cola de los valores más lentos. Con solo el promedio, los grandes retrasos ocasionales quedan ocultos.
| Indicador | Significado | Imagen con 10 000 mediciones |
|---|---|---|
| Promedio | Valor promediado del conjunto | Los picos suelen quedar ocultos |
| p50 | Valor central | Cercano a la sensación habitual |
| p95 | Límite donde empieza a verse el 5 % más lento | Límite tras excluir los 500 casos más lentos |
| p99 | Límite donde empieza a verse el 1 % más lento | Límite tras excluir los 100 casos más lentos |
| p99.9 | Límite donde empieza a verse el 0,1 % más lento | Límite tras excluir los 10 casos más lentos |
| max | Peor valor | El caso más lento de todos |
Supongamos, por ejemplo, que se obtiene la siguiente serie (es un ejemplo para explicar cómo leer los indicadores, no una medición real en un equipo concreto).
- Promedio: 0,8 ms
- p99: 1,2 ms
- p99.9: 3,5 ms
- max: 28 ms
Esto corresponde a un estado en el que normalmente es rápido, pero de vez en cuando presenta picos grandes. En un Windows convencional, el problema real suele aparecer justo en esa cola que va de p99 a max.
Cabe señalar que este artículo no presenta cifras del efecto de la configuración recomendada. La latencia y el jitter cambian fácilmente según la CPU, los controladores, el software residente, la configuración de energía y la forma de aplicar la carga, así que las cifras del entorno de otra persona no sirven tal cual como fundamento para el propio entorno. En su lugar, se deja aquí el procedimiento para elaborar una tabla con la misma forma en su propio entorno.
Procedimiento mínimo para obtener el p99 en su propio entorno
- En el hot path, solo se registra. Se toman el lateness y el tiempo de ejecución con
Stopwatch.GetTimestamp()(oQueryPerformanceCounteren C++) y se escriben en un arreglo reservado de antemano. Aquí no se debe calcular el promedio ni ordenar nada - La agregación se realiza después de detener la medición. Se ordena y se extrae el valor en la posición del percentil correspondiente
- Se repite el mismo procedimiento cambiando las condiciones: antes/después del calentamiento, CA/batería, interfaz en primer plano/minimizada, con o sin carga de otros procesos (5.4)
- Cada vez que se introduce un cambio, se vuelve a medir en las mismas condiciones y se compara
// C# / .NET 8. La agregación se realiza después de detener la medición
using System.Diagnostics;
// En el hot path solo se escribe en el arreglo (cero asignaciones)
long[] latenessTicks = new long[100_000];
int count = 0;
// Ejemplo: dentro del bucle periódico
// 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 es el número de cuentas por segundo, así que al dividir y multiplicar por 1000 se obtienen milisegundos. Si el número de muestras es escaso, el p99.9 pierde sentido. Si va a hablar de p99.9, reúna como mínimo 10 000 muestras (idealmente 100 000).
5.3. Con qué herramientas mirar
El conjunto de herramientas está, en general, bastante definido.
- Medición dentro de la app
Primero se toman por cuenta propia
period / lateness / execution time / queue depth / drop - ETW / WPR / WPA Permite profundizar en CPU, context switch, DPC/ISR y page fault
- LatencyMon Ayuda a detectar sospechosos de variación originada por controladores
- Monitoreo de temperatura / reloj Permite ver el efecto del calor
flowchart LR
accTitle: Herramientas de medición y su aporte a la decisión de mejora
accDescr: Diagrama en el que la medición dentro de la app aporta las distribuciones p50, p95, p99, p99.9 y max, además de miss, drop y profundidad de cola, mientras que ETW/WPR/WPA y el monitoreo de temperatura y reloj aportan al mismo origen de context switch, DPC/ISR y page fault, y todos esos resultados confluyen en decidir la prioridad de las mejoras.
App["Medición dentro de la app"] --> Dist["p50 / p95 / p99 / p99.9 / max"]
App --> Miss["miss / drop / profundidad de cola"]
ETW["ETW / WPR / WPA"] --> Root["context switch / DPC / ISR / page fault"]
Temp["Monitoreo de temperatura / reloj"] --> Root
Dist --> Decide["Decide la prioridad de las mejoras"]
Miss --> Decide
Root --> Decide
Llegar hasta WPA cuesta un poco de trabajo, pero resulta bastante eficaz para distinguir si la causa es el DPC/ISR o si simplemente el propio procesamiento es pesado.
Cómo obtenerlas y el uso mínimo
| Herramienta | Cómo obtenerla | Procedimiento mínimo |
|---|---|---|
| WPR / WPA (Windows Performance Toolkit) | Se instala al elegir «Windows Performance Toolkit» durante la instalación del Windows ADK (Windows Assessment and Deployment Kit). La ubicación predeterminada es C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit |
Abra un símbolo del sistema como administrador y (1) inicie el registro con wpr -start CPU, (2) ejecute el proceso problemático durante decenas de segundos, (3) guarde con wpr -stop trace.etl "investigación de retraso periódico". Después abra trace.etl con WPA. Los nombres de perfil disponibles se consultan con wpr -profiles |
| LatencyMon | Se descarga del sitio de Resplendence Software. La Home Edition para uso personal es gratuita | Inícielo, comience la medición y deje el proceso objetivo funcionando durante varios minutos. Se recopilan el retraso máximo del temporizador del kernel y, por controlador, el tiempo de ejecución de ISR/DPC y los hard pagefault, de modo que anote los controladores con un tiempo de ejecución destacado |
En WPA, lo primero que se mira, dentro de los gráficos relacionados con la CPU, es el tiempo de ejecución de DPC/ISR y el context switch. Ahí aparece «qué controlador retenía la CPU durante el tramo en que el propio hilo no pudo ejecutarse», así que se contrasta con la hora de aparición de los retrasos registrados en 5.1.
Cabe señalar que este artículo no incluye capturas de pantalla. Como la apariencia cambia fácilmente según la versión, siga el procedimiento anterior y compruébelo en la pantalla real.
5.4. Buenas prácticas para las pruebas
Las pruebas no bastan con un entorno de banco tranquilo. Como mínimo, conviene observar por separado estas condiciones.
- Justo después de iniciar, antes del calentamiento
- Después del calentamiento
- Funcionamiento continuo prolongado
- Interfaz en primer plano
- Interfaz minimizada o en un estado cercano a oculto
- Alimentación de CA
- Funcionamiento con batería
- Estado con carga en la red o el disco
Evaluar solo con el entorno de banco facilita pasar por alto los problemas que aparecen en el uso real. Como el comportamiento de un Windows convencional tiende a verse arrastrado por «cómo se usa», es importante verificarlo en condiciones cercanas al uso real.
6. Criterios generales para decidir
La tabla rápida por rango de periodo se adelantó a 1.1 para que pueda volver a ella mientras lee. Aquí se complementan dos decisiones que esa tabla por sí sola no resuelve.
Cuándo decidir que «en un Windows convencional no es posible»
Cuando surge la necesidad de mantener menos de 1 ms durante mucho tiempo y con carga alta, en muchos casos es más rápido decidir sacar fuera solo la parte crítica en tiempo que seguir ajustando. Los candidatos para trasladarla son el firmware del propio dispositivo, un controlador dedicado, un FPGA o un RTOS. El criterio de decisión no debe ser subjetivo, sino basarse en las cifras del capítulo 5.
- El hot path ya no se puede acortar más, y sin embargo el p99.9 y el max siguen superando el requisito
- La causa son perturbaciones externas (DPC/ISR, controladores, otros procesos) que las medidas del lado de la aplicación no alcanzan a resolver (verificado con el aislamiento de 4.7)
- El requisito ha pasado de «no romperse aunque falle alguna vez» a «no puede fallar ni una sola vez»
Cuando se quiere hacer convivir todo en un solo proceso
Si se cargan la GUI, el log, la comunicación y la BD en el mismo bucle del mismo proceso, las necesidades de las etapas posteriores rompen el plazo de las anteriores, porque la espera de flush de un archivo, la reconexión a la BD o el redibujado de la interfaz pueden alargarse, cada uno de ellos, decenas de milisegundos. También es una opción extender la separación entre fast path y slow path (4.2) hasta el nivel de proceso, en lugar de solo el de hilo. Al separar los procesos, aunque la etapa posterior se quede bloqueada, el periodo de la etapa anterior sigue funcionando.
7. Resumen
Hay dos premisas que conviene tener claras.
- En un Windows convencional, el objetivo no es garantizar hard real-time, sino lograr, como soft real-time, una configuración que reduzca la latencia y el jitter y no se rompa aunque ocurra un incumplimiento de plazo
- Lo más eficaz no es ajustar la prioridad, sino ordenar el hot path
En la implementación, lo que resulta eficaz es, más o menos, lo siguiente.
- Separar el fast path y el slow path
- Decidir de antemano la cola de longitud fija y la política para cuando se desborde
- Medir con QPC y esperar con un event o un waitable timer (temporizador esperable)
- Evitar asignaciones, E/S bloqueante y bloqueos pesados en el hot path
En el plano operativo, lo que resulta eficaz es, más o menos, lo siguiente.
- Funcionar con alimentación de CA
- Separar la configuración de energía de producción
- Reducir la carga innecesaria en segundo plano
- Evaluar con p99, p99.9, max y el número de miss
El soft real-time en un Windows convencional no se decide únicamente con la configuración de prioridad; si se afinan por separado el diseño, la implementación, la configuración de energía, la medición y la operación, se puede lograr un sistema bastante estable.
8. Referencias
- 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)
- WPR Command-Line Options
- Download and install the Windows ADK
- Quality of Service (EcoQoS / HighQoS)
- PROCESS_POWER_THROTTLING_STATE structure
- LatencyMon - Resplendence Software
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Diseño de códigos en sistemas empresariales ── Cómo definir códigos de producto y cliente, y el dígito de control
Guía práctica para diseñar códigos de producto y cliente en sistemas empresariales: código significativo frente a secuencial, fórmulas de...
Por qué priorizar la espera por eventos sobre Sleep(1) en Windows
En Windows, la precisión de una espera corta con timeout depende de la granularidad del reloj del sistema y de la planificación. Este art...
Tabla de decisión: terminar o continuar ante una excepción inesperada
Analiza si una aplicación debe terminar o continuar tras una excepción inesperada, según el daño al estado, los efectos externos, los hil...
Por qué usar .NET Generic Host y BackgroundService en una aplicación de escritorio
Resumimos cómo usar Generic Host y BackgroundService en herramientas y apps residentes de Windows para organizar el inicio, el procesamie...
Guía práctica de FileSystemWatcher - Cómo evitar pérdidas de eventos y duplicados
Analizamos el uso de FileSystemWatcher: pérdida de eventos, notificaciones duplicadas, trampas de finalización, reescaneo, claim atómico ...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Se puede hacer procesamiento en tiempo real en Windows?
- No es posible garantizar hard real-time (cero incumplimientos de plazo), pero si se cuidan bien el diseño, la implementación, la medición y la operación, se puede llegar a un estado bastante práctico como soft real-time. El procesamiento periódico de pocos a varias decenas de milisegundos, el manejo de audio/video por búfer, la adquisición de sensores y los bucles de control son realistas incluso en un Windows 10/11 convencional. Por el contrario, si se necesita garantizar cero incumplimientos de plazo o una estabilidad prolongada por debajo de cientos de microsegundos, conviene estudiar trasladar esa parte a un RTOS, un controlador dedicado, un FPGA o el procesamiento en el propio dispositivo.
- ¿Por qué no se debe usar Sleep(1) en un bucle periódico?
- Porque Sleep(1) no produce «un periodo de 1 ms», sino un comportamiento de «esperar al menos 1 ms y sumarle el tiempo de procesamiento», de modo que el exceso de espera se va acumulando tal cual. Es más estable hacer girar el bucle periódico con el plazo absoluto de next += period, dar preferencia a un evento de dispositivo o a un waitable timer de alta precisión para la espera, y limitar el ajuste final a un busy-spin (espera activa) muy corto. timeBeginPeriod debe usarse solo mientras haga falta, revirtiéndolo después.
- ¿Qué es lo más eficaz para reducir la latencia y el jitter?
- Más que subir la prioridad, lo que más ayuda es hacer el hot path corto, de longitud fija y no bloqueante. Se separan el fast path (adquisición y control) y el slow path (guardado, comunicación, interfaz) y se conectan mediante una cola de longitud fija, evitando en el hot path la escritura de archivos, el envío por red, el registro de log pesado y la asignación de memoria en cada iteración. En el plano operativo ayudan la alimentación de CA, revisar el modo de energía, comprobar EcoQoS y ordenar la carga en segundo plano.
- ¿Con qué indicadores debo evaluar la estabilidad del procesamiento periódico?
- No basta con el promedio: hay que mirar p99, p99.9, max y el número de missed deadline. Por ejemplo, un promedio de 0,8 ms con un max de 28 ms indica un estado que suele ser rápido pero que de vez en cuando presenta picos grandes, y en un Windows convencional el problema real suele aparecer justo en esa cola que va de p99 a max. Conviene registrar también los picos de DPC/ISR, los page fault, la profundidad de la cola y las variaciones de temperatura y reloj, y evaluar por separado condiciones como antes/después del calentamiento, funcionamiento prolongado, estado minimizado o alimentación por batería.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.