Переменная производительность занятого цикла ожидания?

Я оцениваю производительность занятого цикла ожидания для запуска событий через равные промежутки времени. Я заметил странное поведение, используя следующий код:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int timespec_subtract(struct timespec *, struct timespec, struct timespec);

int main(int argc, char *argv[]) {
    int iterations = atoi(argv[1])+1;

    struct timespec t[2], diff;

    for (int i = 0; i < iterations; i++) {
        clock_gettime(CLOCK_MONOTONIC, &t[0]);

        static volatile int i;
        for (i = 0; i < 200000; i++)
            ;

        clock_gettime(CLOCK_MONOTONIC, &t[1]);

        timespec_subtract(&diff, t[1], t[0]);
        printf("%ld\n", diff.tv_sec * 1000000000 + diff.tv_nsec);
    }
}

На тестовой машине (двойной 14-ядерный E5-2683 v3 @ 2,00 ГГц, 256 ГБ DDR4) 200 000 итераций цикла for составляют приблизительно 1 мс. А может и нет:

1030854
1060237
1012797
1011479
1025307
1017299
1011001
1038725
1017361
... (about 700 lines later)
638466
638546
638446
640422
638468
638457
638468
638398
638493
640242
... (about 200 lines later)
606460
607013
606449
608813
606542
606484
606990
606436
606491
606466
... (about 3000 lines later)
404367
404307
404309
404306
404270
404370
404280
404395
404342
406005

Когда времена смещаются в третий раз, они остаются в основном постоянными (в течение примерно 2 или 3 микросекунд), за исключением случаев, когда время от времени они прыгают до 450 мкс в течение нескольких сотен итераций. Такое поведение повторяется на аналогичных машинах и в течение многих прогонов.

Я понимаю, что занятые циклы могут быть оптимизированы компилятором, но я не думаю, что это проблема здесь. Я не думаю, что кеш должен влиять на него, потому что не должно происходить аннулирования, и это не объясняет внезапную оптимизацию. Я также попытался использовать регистр int для счетчика цикла, без заметного эффекта.

Любые мысли о том, что происходит, и как сделать это (более) последовательным?

РЕДАКТИРОВАТЬ: Для информации, при запуске этой программы с usleep, nanosleep или показанным занятым ожиданием 10 000 итераций все показывают ~20000 непроизвольных переключений контекста с time -v,

2 ответа

Одна большая проблема с занятым ожиданием состоит в том, что, помимо использования ресурсов ЦП, время ожидания будет сильно зависеть от скорости блока ЦП. Таким образом, один и тот же цикл может выполняться в разное время на разных машинах.

Проблема с любым методом сна состоит в том, что из-за планирования ОС вы можете в конечном итоге спать дольше, чем предполагалось. Страницы руководства для nanosleep говорит, что будет использовать rem аргумент, чтобы сказать вам оставшееся время в случае, если вы получили сигнал, но он ничего не говорит о слишком долгом ожидании.

Вам нужно захватить метку времени после каждого звонка usleep так что вы знаете, как долго вы на самом деле спали. Если вы спали слишком коротко, вы добавляете дефицит. Если вы спали слишком долго, вы вычитаете переизбыток.

Вот пример того, как я сделал это в UFTP, приложении для многоадресной передачи файлов, для отправки пакетов с постоянной скоростью:

int64_t diff_usec(struct timeval t2, struct timeval t1)
{
    return (t2.tv_usec - t1.tv_usec) +
            (int64_t)1000000 * (t2.tv_sec - t1.tv_sec);
}

...

        int32_t packet_wait = 10000;
        int64_t overage = 0, tdiff;
        struct timeval current_sent, last_sent;

        gettimeofday(&last_sent, NULL);

        while(...) {
            ...

            if (packet_wait > overage) {
                usleep(packet_wait - (int32_t)overage);
            }
            gettimeofday(&current_sent, NULL);
            tdiff = diff_usec(current_sent, last_sent);
            overage += tdiff - packet_wait;

            last_sent = current_sent;
            ...
        }

Я бы сделал 2 балла - из-за переключения контекста режим сна / сна может спать дольше, чем ожидалось. Более того, если есть какая-то задача с более высоким приоритетом, например, прерывания, может возникнуть ситуация, когда режим сна может вообще не выполняться.

Таким образом, если вам нужна точная задержка в вашем приложении, вы можете использовать gettimeofday, чтобы вычислить временной промежуток, который можно вычесть из задержки в вызове sleep / usleep.

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