simbolo milisegundo kilosegundos c timer posix real-time poisson

milisegundo - Programación de eventos en granularidad microsegundo en POSIX



milisegundo simbolo (2)

Estoy tratando de determinar la granularidad que puedo programar con precisión para que ocurran las tareas en C / C ++. Por el momento, puedo programar tareas de manera confiable cada 5 microsegundos, pero estoy tratando de ver si puedo reducir esto aún más.

Cualquier consejo sobre cómo lograr esto / si es posible sería muy apreciado.

Como sé que la granularidad del temporizador a menudo puede depender del sistema operativo: actualmente estoy ejecutando Linux, pero usaría Windows si la granularidad de tiempo es mejor (aunque no creo que lo sea, basado en lo que he encontrado para el QueryPerformanceCounter)

Ejecuto todas las mediciones en bare-metal (sin VM). /proc/timer_info confirma la resolución del temporizador en nanosegundos para mi CPU (pero sé que eso no se traduce en una resolución de alarma de un nanosegundo)

Corriente

Mi código actual se puede encontrar como un Gist aquí

Por el momento, puedo ejecutar una solicitud cada 5 microsegundos (5000 nanosegundos) con menos del 1% de llegadas tardías. Cuando ocurren llegadas tardías, generalmente están a un solo ciclo (5000 nanosegundos) detrás.

Estoy haciendo 3 cosas en este momento

Establecer el proceso en prioridad en tiempo real (algunos señalados por @ Spudd86 aquí )

struct sched_param schedparm; memset(&schedparm, 0, sizeof(schedparm)); schedparm.sched_priority = 99; // highest rt priority sched_setscheduler(0, SCHED_FIFO, &schedparm);

Minimizando la holgura del temporizador

prctl(PR_SET_TIMERSLACK, 1);

Usando timerfds (parte del kernel Linux 2.6)

int timerfd = timerfd_create(CLOCK_MONOTONIC,0); struct itimerspec timspec; bzero(&timspec, sizeof(timspec)); timspec.it_interval.tv_sec = 0; timspec.it_interval.tv_nsec = nanosecondInterval; timspec.it_value.tv_sec = 0; timspec.it_value.tv_nsec = 1; timerfd_settime(timerfd, 0, &timspec, 0);

Posibles mejoras

  1. Dedicar un procesador a este proceso?
  2. Use un dff con temporizador sin bloqueo para poder crear un bucle cerrado, en lugar de bloquear (el bucle cerrado desperdiciará más CPU, pero también puede ser más rápido para responder a una alarma)
  3. Usar un dispositivo embebido externo para disparar (no puedo imaginar por qué esto sería mejor)

Por qué

Actualmente estoy trabajando en la creación de un generador de carga de trabajo para un motor de evaluación comparativa. El generador de carga de trabajo simula una tasa de llegada (solicitudes X / segundo, etc.) usando un proceso de Poisson. A partir del proceso de Poisson, puedo determinar los tiempos relativos en los que se deben realizar las solicitudes desde el motor de evaluación comparativa.

Entonces, por ejemplo, a las 10 solicitudes por segundo, podemos tener solicitudes realizadas en: t = 0.02, 0.04, 0.05, 0.056, 0.09 segundos

Estas solicitudes deben programarse por adelantado y luego ejecutarse. A medida que aumenta el número de solicitudes por segundo, aumenta la granularidad requerida para programar estas solicitudes (miles de solicitudes por segundo requieren una precisión inferior a milisegundos). Como resultado, estoy tratando de descubrir cómo escalar este sistema aún más.


Estás muy cerca de los límites de lo que vainilla Linux te ofrecerá, y es mucho más allá de lo que puede garantizar. Agregar los parches en tiempo real a su kernel y sintonizarlos para una preferencia anticipada le ayudará a obtener mejores garantías bajo carga. También eliminaría cualquier asignación de memoria dinámica de su código de tiempo crítico, malloc y amigos pueden (y lo harán) atascar durante un período de tiempo no inconsecuente (en un sentido en tiempo real) si tiene que recuperar la memoria del i / o caché. También estaría considerando eliminar el intercambio de esa máquina para ayudar a garantizar el rendimiento. Dedicar un procesador a su tarea ayudará a evitar tiempos de cambio de contexto pero, una vez más, no es garantía.

También te sugiero que tengas cuidado con ese nivel de prioridad_sched, estás por encima de varios bits importantes de Linux que pueden generar efectos muy extraños.


Lo que gana al construir un núcleo en tiempo real son las garantías más confiables (es decir, la latencia máxima más baja) del tiempo entre un evento IO / temporizador manejado por el kernel, y el control que se pasa a su aplicación en respuesta. Esto tiene el precio de un menor rendimiento, y es posible que observe un aumento en los tiempos de latencia de su mejor caso.

Sin embargo, la única razón para usar temporizadores de sistema operativo para programar eventos con alta precisión es si tiene miedo de grabar ciclos de CPU en un bucle mientras espera su próximo evento vencido. Los temporizadores de sistema operativo ( especialmente en MS Windows) no son confiables para eventos de temporización de alta granularidad, y son muy dependientes del tipo de hardware de tiempo / HPET disponible en su sistema.

Cuando requiero una programación de eventos altamente precisa, uso un método híbrido. En primer lugar, mido la peor latencia de casos, es decir, la mayor diferencia entre el tiempo que solicité para dormir y el tiempo real del reloj después de dormir. Llamemos a esta diferencia "D". (En realidad, puede hacer esto sobre la marcha durante el funcionamiento normal, mediante el seguimiento de "D" cada vez que duerme, con algo como "D = (D * 7 + lastD) / 8" para producir un promedio temporal).

Entonces nunca solicite dormir más allá de "N - D * 2", donde "N" es la hora del próximo evento. Cuando esté dentro del tiempo "D * 2" del siguiente evento, ingrese un bucle giratorio y espere a que aparezca "N".

Esto consume muchos más ciclos de CPU, pero dependiendo de la precisión que requiera, es posible que pueda salirse con la "sched_yield ()" en su bucle de giro, que es más amable con su sistema.