Является ли clock_gettime() достаточным для субмикросекундной синхронизации?

Мне нужен таймер с высоким разрешением для встроенного профилировщика в сборке Linux нашего приложения. Наши показатели профилировщика ограничены как индивидуальные функции, поэтому для этого требуется точность таймера более 25 наносекунд.

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

Поэтому я попытался использовать функцию clock_gettime вместо запроса CLOCK_PROCESS_CPUTIME_ID. Документы утверждают, что это дает мне наносекундное время, но я обнаружил, что накладные расходы на один вызов clock_gettime() превысили 250 нс. Это делает невозможным длительность событий продолжительностью 100 нс и наличие таких высоких накладных расходов на функции таймера серьезно увеличивает производительность приложения, искажая профили вне значения. (У нас есть сотни тысяч профилирующих узлов в секунду.)

Есть ли способ вызвать clock_gettime(), который имеет меньше, чем & frac14; & mu; s накладные расходы? Или есть ли другой способ, с помощью которого я могу надежно получить счетчик временной отметки с накладными расходами < 25ns? Или я застрял с помощью rdtsc?

Ниже приведен код, который я использовал для времени clock_gettime().

// calls gettimeofday() to return wall-clock time in seconds:
extern double Get_FloatTime();
enum { TESTRUNS = 1024*1024*4 };

// time the high-frequency timer against the wall clock
{
    double fa = Get_FloatTime();
    timespec spec; 
    clock_getres( CLOCK_PROCESS_CPUTIME_ID, &spec );
    printf("CLOCK_PROCESS_CPUTIME_ID resolution: %ld sec %ld nano\n", 
            spec.tv_sec, spec.tv_nsec );
    for ( int i = 0 ; i < TESTRUNS ; ++ i )
    {
        clock_gettime( CLOCK_PROCESS_CPUTIME_ID, &spec );
    }
    double fb = Get_FloatTime();
    printf( "clock_gettime %d iterations : %.6f msec %.3f microsec / call\n",
        TESTRUNS, ( fb - fa ) * 1000.0, (( fb - fa ) * 1000000.0) / TESTRUNS );
}
// and so on for CLOCK_MONOTONIC, CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID.

Результаты:

CLOCK_PROCESS_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 3115.784947 msec 0.371 microsec / call
CLOCK_MONOTONIC resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2505.122119 msec 0.299 microsec / call
CLOCK_REALTIME resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2456.186031 msec 0.293 microsec / call
CLOCK_THREAD_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2956.633930 msec 0.352 microsec / call

Это стандартное ядро ​​Ubuntu. Приложение представляет собой порт приложения Windows (где наша встроенная сборка rdtsc работает отлично).

Добавление:

Имеет ли x86-64 GCC некоторый собственный эквивалент __ rdtsc(), поэтому я могу, по крайней мере, избежать встроенной сборки?

Ответ 1

Нет. Для этого вам придется использовать код для конкретной платформы. На x86 и x86-64 вы можете использовать 'rdtsc' для чтения Time Stamp Counter.

Просто соедините сборку rdtsc, которую вы используете.

__inline__ uint64_t rdtsc(void) {
  uint32_t lo, hi;
  __asm__ __volatile__ (      // serialize
  "xorl %%eax,%%eax \n        cpuid"
  ::: "%rax", "%rbx", "%rcx", "%rdx");
  /* We cannot use "=A", since this would use %rax on x86_64 and return only the lower 32bits of the TSC */
  __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
  return (uint64_t)hi << 32 | lo;
}

Ответ 2

Я провел несколько тестов на своей системе, которая представляет собой четырехъядерный E5645 Xeon, поддерживающий постоянное ядро ​​3.2.54 TSC, и результаты были следующими:

clock_gettime(CLOCK_MONOTONIC_RAW)       100ns/call
clock_gettime(CLOCK_MONOTONIC)           25ns/call
clock_gettime(CLOCK_REALTIME)            25ns/call
clock_gettime(CLOCK_PROCESS_CPUTIME_ID)  400ns/call
rdtsc (implementation @DavidSchwarz)     600ns/call

Таким образом, похоже, что в разумно современной системе (принятый ответ) rdtsc является наихудшим маршрутом для перехода вниз.

Ответ 3

Мне нужен таймер с высоким разрешением для встроенного профилировщика в сборке Linux нашего приложения. Наши показатели профилировщика ограничены как индивидуальные функции, поэтому для этого требуется точность таймера более 25 наносекунд.

Рассматривали ли вы oprofile или perf? Вы можете использовать аппаратное обеспечение счетчика производительности на вашем CPU, чтобы получить данные профилирования без добавления инструментов для самого кода. Вы можете видеть данные по каждой функции или даже по строке кода. "Единственным" недостатком является то, что он не будет измерять время настенного времени, потребляемое, оно будет измерять время процессора, поэтому оно не подходит для всех исследований.

Ответ 4

Дайте clockid_t CLOCK_MONOTONIC_RAW попробовать?

CLOCK_MONOTONIC_RAW (с Linux 2.6.28; для Linux)             Подобно CLOCK_MONOTONIC, но обеспечивает доступ к             необработанное аппаратное время, которое не подлежит NTP             корректировки или инкрементные корректировки, выполняемые             adjtime (3).

Из Man7.org

Ответ 5

Вы вызываете clock_getttime с параметром управления, что означает, что api разветвляется через дерево if-else, чтобы узнать, какое время вы хотите. Я знаю, что вы не можете избежать этого с помощью этого вызова, но посмотрите, можете ли вы вникнуть в системный код и вызвать то, что ядро ​​в конечном итоге вызывает напрямую. Кроме того, я отмечаю, что вы включаете время цикла (i ++ и условную ветвь).