clock_gettime может быть очень медленным даже при использовании VDSO

Я использую CentOS Linux версии 7.3.1611 на процессоре Intel (R) Xeon (R) E5-2667 v4 @ 3,20 ГГц

Во время тестирования моего приложения в пользовательском пространстве я заметил, что clock_gettime (CLOCK_MONOTONIC, & ts) может занимать до 5-6 микросекунд вместо примерно 23 наносекунд в среднем. Это может произойти только один раз на 10000 последовательных вызовов, но это может случиться.

Если бы не было библиотеки VDSO, это можно было бы объяснить. Однако VDSO используется для каждого clock_gettime (я проверял это с помощью strace).

Независимо от того, привязан ли соответствующий поток к определенному ядру процессора или нет. Независимо от того, изолировано это ядро ​​процессора от ОС или нет. Это означает, что тестовое приложение может работать на эксклюзивном ядре ЦП, но в любом случае может появиться отставание!

Я измеряю задержку, сравнивая результаты двух последовательных вызовов clock_gettime, например:

unsigned long long __gettimeLatencyNs() {
    struct timespec t1_ts;
    struct timespec t2_ts;
    clock_gettime(CLOCK_MONOTONIC, &t1_ts);
    clock_gettime(CLOCK_MONOTONIC, &t2_ts);
    return ((t2_ts.tv_sec - t1_ts.tv_sec)*NANO_SECONDS_IN_SEC + t2_ts.tv_nsec - t1_ts.tv_nsec);
}  

Может кто поделится идеями, что там может быть не так?


person Konstantin Utkin    schedule 24.08.2017    source источник


Ответы (2)


Давайте посмотрим на исходный код для clock_gettime:

/* Code size doesn't matter (vdso is 4k anyway) and this is faster. */
notrace static int __always_inline do_realtime(struct timespec *ts)
{
    unsigned long seq;
    u64 ns;
    int mode;

    do {
        seq = gtod_read_begin(gtod);
        mode = gtod->vclock_mode;
        ts->tv_sec = gtod->wall_time_sec;
        ns = gtod->wall_time_snsec;
        ns += vgetsns(&mode);
        ns >>= gtod->shift;
    } while (unlikely(gtod_read_retry(gtod, seq)));

    ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
    ts->tv_nsec = ns;

    return mode;
}

Здесь мы видим, что код выполняется внутри цикла. Этот цикл помечен условием unlikely. Условие связано с тем, что этот код читает совместно используемую память, которая иногда обновляется, и пока она обновляется, код должен дождаться завершения обновления.

Таким образом, наиболее вероятный ответ на ваш вопрос заключается в том, что время от времени вы ловите clock_gettime, когда соответствующий код ядра обновляет свои структуры. Когда это происходит, код работает значительно медленнее.

person Shachar Shemesh    schedule 24.08.2017
comment
Кажется, что не только в синтетическом тесте вроде этого можно встретить ожидание, пока ядро ​​что-то обновляет. И это довольно странно, если ядру нужно несколько микросекунд просто на какое-то обновление памяти ... - person Konstantin Utkin; 25.08.2017

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

То есть, любой тип цикла синхронизации может быть прерван внешними событиями, такими как прерывания. Например, за исключением очень конкретной конфигурации ядра без тиков (не по умолчанию), ваше приложение будет периодически прерываться из-за прерывания часов, которое будет выполнять некоторую обработку, чтобы увидеть, должен ли запускаться другой процесс. Даже если в конечном итоге ни один другой процесс не завершится, это может легко составить несколько микросекунд.

Кроме того, оборудование может временно останавливаться по разным причинам, например, переходы частоты, которые происходят, когда другие ядра входят или выходят. состояние простоя. Я измерил эти переходы примерно за 8 микросекунд, что близко к значению, которое вы сообщаете. Во время этих пауз ЦП не выполняет инструкции, но TSC продолжает работать, поэтому он отображается как сверхдлинный интервал.

Помимо этого, существует масса причин, по которым у вас могут возникнуть выбросы по времени. Этот ответ также включает способы, с помощью которых вы могли бы сузить круг возможных причин, если это вас интересует.

Наконец, ответ предполагает, что clock_gettime сам может блокировать, пока ядро ​​обновляет структуру данных. Хотя это, безусловно, возможно, но я думаю, что это менее вероятно, чем другие причины. Вы можете скопировать и вставить код VDSO, а затем изменить его, чтобы записать, действительно ли произошло какое-либо блокирование, и вызвать его, чтобы увидеть, коррелируют ли ваши паузы с блокировкой. Думаю, нет.

person BeeOnRope    schedule 08.12.2018