Какова точность интервальных таймеров в Linux?

Я пытаюсь охарактеризовать дрожание таймера в Linux. Моя задача состояла в том, чтобы запустить таймеры 100 мс и посмотреть, как работают числа.

Я работаю над многоядерной машиной. Я использовал стандартную пользовательскую программу с setitimer(), ту же работу, что и root, а затем с близостью процессора и, наконец, с близостью процессора и приоритетом процесса. Затем я запускал то же самое с ядром PREEMPT_RT, а затем запускал примеры с помощью clock_nanosleep(), как в демо-коде на странице PREEMPT_RT. Из всех прогонов производительность таймера была очень схожей, без реальной разницы, несмотря на изменения.

Наша цель - постоянный таймер. Лучшим худшим случаем, который я мог получить регулярно, было около 200us. Гистограмма для всех случаев показывает действительно странное поведение. Во-первых, я не ожидал, что таймеры будут срабатывать раньше. Но они это делают. И, как вы можете видеть на гистограмме, я получаю впадины с обеих сторон смещения 0. Они видны в трех полосах на втором графике. На первом графике ось X находится в микросекундах. На втором графике ось Y находится в микросекундах.

Я выполнил 30-секундный тест (то есть 300 событий таймера) 100 раз, чтобы сгенерировать некоторые числа. Вы можете увидеть их на следующих диаграммах. В 200us большое падение. Все 30000 тайм-часов смещаются на второй график, где вы можете увидеть некоторые выбросы.

X axis is in microsecondsY axis is in microseconds

Итак, вопрос в том, кто-нибудь еще сделал такой анализ раньше? Вы видели такое же поведение? Мое предположение заключается в том, что ядро ​​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);
    }
}

EDIT: это на Xeon E31220L, работающем на частоте 2,2 ГГц, при запуске x86_64 Fedora Core 19.

Ответ 1

Вы правы, чтобы не ожидать, что таймеры будут срабатывать раньше - и они этого не делают. Очевидная ранняя стрельба заключается в том, что вы не измеряете время, прошедшее с момента истечения предыдущего таймера - вы измеряете время с предыдущего вызова gettimeofday(). Если между истечением таймера и задержкой по истечении заданного времени была достигнута задержка, вы увидите, что этот gettimeofday() работает с задержкой, а следующий - с той же самой скоростью.

Вместо того, чтобы регистрировать разницу между последующими вызовами gettimeofday(), попробуйте выполнить регистрацию возвращаемых абсолютных времен, а затем сравните возвращаемые времена с N * 100 мс после начального времени.

Если вы хотите, чтобы PREEMPT_RT вам помог, вам нужно будет установить политику планировщика в реальном времени для вашей тестовой программы (SCHED_FIFO или SCHED_RR), для которой требуется root.