Самокорректирующийся периодический таймер с использованием gettimeofday()

У меня есть цикл, который запускает каждый X usecs, который состоит из выполнения некоторого ввода-вывода, а затем спит для оставшейся части X usecs. Чтобы (примерно) рассчитать время ожидания, все, что я делаю, это беру временную метку до и после ввода / вывода и вычитаю разницу из X. Вот функция, которую я использую для временной метки:

long long getus ()
{
        struct timeval time;
        gettimeofday(&time, NULL);
        return (long long) (time.tv_sec + time.tv_usec);
}

Как вы можете себе представить, это начинает дрейфовать довольно быстро, и фактическое время между пакетами ввода-вывода обычно на несколько мс больше, чем X. Чтобы попытаться сделать это немного более точным, я подумал, может быть, если я буду вести учет предыдущая начальная временная метка, каждый раз, когда я начинаю новый цикл, я могу вычислить, сколько времени занимал предыдущий цикл (время между этой начальной временной меткой и предыдущей). Затем я знаю, сколько это было больше, чем X, и я могу изменить свой сон, чтобы компенсировать этот цикл.

Вот как я пытаюсь это реализовать:

    long long start, finish, offset, previous, remaining_usecs;
    long long delaytime_us = 1000000;

    /* Initialise previous timestamp as 1000000us ago*/
    previous = getus() - delaytime_us;
    while(1)
    {
            /* starting timestamp */
            start = getus();

            /* here is where I would do some I/O */

            /* calculate how much to compensate */
            offset = (start - previous) - delaytime_us;

            printf("(%lld - %lld) - %lld = %lld\n", 
                    start, previous, delaytime_us, offset);

            previous = start;

            finish = getus();

            /* calculate to our best ability how long we spent on I/O.
             * We'll try and compensate for its inaccuracy next time around!*/
            remaining_usecs = (delaytime_us - (finish - start)) - offset;

            printf("start=%lld,finish=%lld,offset=%lld,previous=%lld\nsleeping for %lld\n",
                    start, finish, offset, previous, remaining_usecs);

            usleep(remaining_usecs);

    }

Похоже, что он работает на первой итерации цикла, однако после этого все портится.

Вот вывод для 5 итераций цикла:

(1412452353 - 1411452348) - 1000000 = 5
start=1412452353,finish=1412458706,offset=5,previous=1412452353
sleeping for 993642

(1412454788 - 1412452353) - 1000000 = -997565
start=1412454788,finish=1412460652,offset=-997565,previous=1412454788
sleeping for 1991701

(1412454622 - 1412454788) - 1000000 = -1000166
start=1412454622,finish=1412460562,offset=-1000166,previous=1412454622
sleeping for 1994226

(1412457040 - 1412454622) - 1000000 = -997582
start=1412457040,finish=1412465861,offset=-997582,previous=1412457040
sleeping for 1988761

(1412457623 - 1412457040) - 1000000 = -999417
start=1412457623,finish=1412463533,offset=-999417,previous=1412457623
sleeping for 1993507

Первая строка вывода показывает, как рассчитывалось время предыдущего цикла. Похоже, что первые две метки времени находятся в основном на расстоянии 1000000 мкс (1412452353 - 1411452348 = 1000005). Однако после этого расстояние между начальными временными метками начинает выглядеть не таким разумным, как и смещение. Кто-нибудь знает, что я здесь делаю не так?

РЕДАКТИРОВАТЬ: Я также приветствовал бы предложения о лучших способах получить точный таймер и иметь возможность спать во время задержки!

1 ответ

После еще одного исследования я обнаружил две вещи не так: во-первых, я неправильно вычисляю метку времени. getus() должен возвращаться так:

return (long long) 1000000 * (time.tv_sec + time.tv_usec);

А во-вторых, я должен хранить метку времени в unsigned long long или же uint64_t, Так что getus () должен выглядеть так:

uint64_t getus ()
{
        struct timeval time;
        gettimeofday(&time, NULL);
        return (uint64_t) 1000000 * (time.tv_sec + time.tv_usec);
}

Я не смогу проверить это до завтра, поэтому я сообщу.

Другие вопросы по тегам