Какова точность интервальных таймеров в Linux?
Я пытаюсь охарактеризовать джиттер таймера в Linux. Моей задачей было запустить таймеры на 100 мс и посмотреть, как работают цифры.
Я работаю на многоядерной машине. Я использовал стандартную пользовательскую программу с setitimer(), которая запускалась с правами root, затем с привязкой к процессору и, наконец, с привязкой к процессору и с приоритетом процесса. Затем я запустил то же самое с ядром PREEMPT_RT, а затем запустил примеры с помощью clock_nanosleep(), как в демонстрационном коде на странице PREEMPT_RT. Из всех запусков производительность таймера была очень похожа, без существенной разницы, несмотря на изменения.
Наша конечная цель - постоянный таймер. Лучший худший случай, который я мог получить регулярно, составлял приблизительно 200 мкс. Гистограмма для всех случаев показывает действительно странное поведение. С одной стороны, я бы не ожидал, что таймеры сработают рано. Но они делают. И как вы можете видеть на гистограмме, я получаю впадины по обе стороны от смещения 0. Они видны в трех полосах на втором графике. На первом графике ось X указана в микросекундах. На втором графике ось Y находится в микросекундах.
Я провел тест 30-х годов (то есть 300 событий таймера) 100 раз, чтобы сгенерировать некоторые числа. Вы можете увидеть их на следующих диаграммах. На 200 мс большой спад. Все 30000 смещений часов таймера отображаются на втором графике, где вы можете увидеть некоторые выбросы.
Итак, вопрос в том, кто-нибудь еще делал такой анализ раньше? Вы видели такое же поведение? Я предполагаю, что ядро RT помогает в системах с большой нагрузкой, но в нашем случае это не помогло устранить дрожание таймера. Это твой опыт?
Вот код Как я уже говорил ранее, я изменил пример кода на сайте PREEMPT_RT, который использует функцию clock_nanosleep(), поэтому я не буду вносить в него свои минимальные изменения.
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <stdlib.h>
#define US_PER_SEC 1000000
#define WAIT_TIME 100000
#define MAX_COUNTER 300
int counter = 0;
long long last_time = 0;
static long long times[MAX_COUNTER];
int i = 0;
struct sigaction sa;
void timer_handler(int signum)
{
if (counter > MAX_COUNTER)
{
sigaction(SIGALRM, &sa, NULL);
for (i = 0; i < MAX_COUNTER; i++)
{
printf("%ld\n", times[i]);
}
exit(EXIT_SUCCESS);
}
struct timeval t;
gettimeofday(&t, NULL);
long long elapsed = (t.tv_sec * US_PER_SEC + t.tv_usec);
if (last_time != 0)
{
times[counter] = elapsed - last_time;
++counter;
}
last_time = elapsed;
}
int main()
{
struct itimerval timer;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &timer_handler;
sigaction(SIGALRM, &sa, NULL);
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 1;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = WAIT_TIME;
setitimer(ITIMER_REAL, &timer, NULL);
while (1)
{
sleep(1);
}
}
РЕДАКТИРОВАТЬ: это на Xeon E31220L, работает на 2,2 ГГц, работает под управлением x86_64 Fedora Core 19.
1 ответ
Вы правы, не ожидая, что таймеры сработают рано, а они нет. Очевидное раннее срабатывание заключается в том, что вы не измеряете время с момента истечения предыдущего таймера - вы измеряете время с предыдущего gettimeofday()
вызов. Если произошла задержка между истечением таймера и запланированным процессом, то вы увидите это gettimeofday()
опаздываете, а следующий забегает на столько же.
Вместо регистрации разницы между последующими gettimeofday()
вызовы, попробуйте записать абсолютное время, возвращенное, а затем сравнить возвращенное время с N * 100 мс после начального времени.
Если ты хочешь PREEMPT_RT
чтобы помочь вам, вам нужно будет установить политику планировщика в реальном времени для вашей тестовой программы (SCHED_FIFO
или же SCHED_RR
), который требует root.
Я внес некоторые изменения в ваш код и в основном заменил timer
как показано ниже, и запустите процесс как ход RT (SCHED_FIFO).
setitimer() -> timer_create()/timer_settime()
gettimeofday() -> clock_gettime()
Мой тестовый стенд - это процессор i9-9900k и пропатченный Linux PREEMPT-RT с ядром 5.0.21. Временной интервал таймера составляет 1 мс, и программа работает около 10 часов, чтобы получить следующий результат.
Я также бегу Cyclictest
(на основе nanosleep()
) на моей машине, и он показывает лучший контроль задержки (максимальная задержка менее 15 мкс). Итак, на мой взгляд, если вы хотите реализовать таймер с высоким разрешением самостоятельно, может оказаться полезным автономный поток RT, выполняющий nanosleep на изолированном ядре. Я новичок в системе RT, комментарии приветствуются.