Более быстрый эквивалент gettimeofday

При попытке создать очень чувствительное к задержке приложение, которое должно отправлять 100 сообщений в секунду, каждое сообщение, имеющее поле времени, мы хотели рассмотреть возможность оптимизации gettimeofday. Первоначально мысль была оптимизирована на rdtsc. Есть предположения? Любые другие указатели? Требуемая точность возвращаемого значения времени - миллисекунды, но это не имеет большого значения, если значение иногда не синхронизируется с приемником на 1-2 миллисекунды. Попытка сделать лучше, чем 62 наносекунды gettimeofday принимает

Ответ 1

Действительно ли вы тестировали и обнаружили, что gettimeofday является неприемлемо медленным?

При скорости 100 сообщений в секунду у вас есть 10 мс процессорного времени на сообщение. Если у вас несколько ядер, предполагая, что он может быть полностью распараллелен, вы можете легко увеличить его на 4-6x - это 40-60 мс на сообщение! Стоимость gettimeofday вряд ли будет где-то около 10 мс - я бы предположил, что она больше похожа на 1-10 микросекунд (в моей системе, microbenchmarking она дает около 1 микросекунды за звонок - попробуйте это для себя). Ваши усилия по оптимизации будут лучше потрачены в других местах.

При использовании TSC разумная идея, у современной Linux уже есть пользовательское пространство TSC-based gettimeofday - где это возможно, vdso будет втягивать реализация gettimeofday, которая применяет смещение (чтение из разделяемого сегмента памяти ядра пользователя) до значения rdtsc, таким образом вычисляя время суток без ввода ядра. Тем не менее, некоторые модели ЦП не имеют синхронизации TSC между различными ядрами или разными пакетами, и поэтому это может быть отключено. Если вам нужна высокая производительность, вы можете сначала подумать о поиске модели ЦП, у которой есть синхронизированный TSC.

Тем не менее, если вы готовы пожертвовать значительным разрешением (ваше время будет только точным до последнего тика, а это значит, что оно может быть отключено на десятки миллисекунд), вы можете использовать CLOCK_MONOTONIC_COARSE или CLOCK_REALTIME_COARSE с clock_gettime. Это также реализовано с помощью vdso, и гарантированно не вызывать ядро ​​(для последних ядер и glibc).

Ответ 2

Часы POSIX

Я написал тест для источников синхронизации POSIX:

  • время = > 3 цикла
  • ftime (ms) = > 54 цикла
  • gettimeofday (us) = > 42 цикла
  • clock_gettime (ns) = > 9 циклов (CLOCK_MONOTONIC_COARSE)
  • clock_gettime (ns) = > 9 циклов (CLOCK_REALTIME_COARSE)
  • clock_gettime (ns) = > 42 цикла (CLOCK_MONOTONIC)
  • clock_gettime (ns) = > 42 цикла (CLOCK_REALTIME)
  • clock_gettime (ns) = > 173 цикла (CLOCK_MONOTONIC_RAW)
  • clock_gettime (ns) = > 179 циклов (CLOCK_BOOTTIME)
  • clock_gettime (ns) = > 349 циклов (CLOCK_THREAD_CPUTIME_ID)
  • clock_gettime (ns) = > 370 циклов (CLOCK_PROCESS_CPUTIME_ID)
  • rdtsc (циклы) = > 24 цикла

Эти номера относятся к процессору Intel Core i7-4771 @3,50 ГГц на Linux 4.0. Эти измерения проводились с использованием регистра TSC и каждый раз выполняли каждый тактовый метод и принимали минимальное себестоимость.

Вы хотите протестировать на компьютерах, которые собираетесь использовать, но как они реализованы, варьируется от версии аппаратного обеспечения и ядра. Код можно найти здесь. Он полагается на регистр TSC для подсчета циклов, который находится в одном и том же репо (tsc.h).

TSC

Доступ к TSC (счетчик времени обработки процессора) является самым точным и самым дешевым способом времени. Как правило, это то, что ядро ​​использует самостоятельно. Это также довольно прямолинейно на современных процессорах Intel, поскольку TSC синхронизирован по ядрам и не подвержен влиянию частотного масштабирования. Таким образом, он обеспечивает простой, глобальный источник времени. Вы можете увидеть пример использования здесь с прохождением кода сборки здесь.

Основная проблема с этим (кроме переносимости) заключается в том, что, похоже, нет хорошего способа перейти от циклов к наносекундам. Intel документирует, насколько я могу найти, что TSC работает на фиксированной частоте, но эта частота может отличаться от указанной частоты процессоров. Похоже, что Intel не обеспечивает надежный способ определения частоты TSC. Ядро Linux, похоже, решает это, проверяя, сколько циклов TSC происходит между двумя аппаратными таймерами (см. здесь).

Memcached

Memcached пытается использовать метод кэширования. Это может быть просто убедиться, что производительность более предсказуема на разных платформах или лучше масштабируется с несколькими ядрами. Это также не может быть целесообразной оптимизацией.

Ответ 3

Как и bdonian, если вы отправляете только несколько сотен сообщений в секунду, gettimeofday будет достаточно быстрым.

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

  • имеет глобальную переменную, дающую текущую временную метку в вашей желаемой точности.
  • имеет выделенный фоновый поток, который ничего не делает, кроме обновления метки времени (если временная метка должна обновляться каждые T единиц времени, затем нужно, чтобы поток запустил некоторую долю T, а затем обновил временную метку; используйте функции реального времени, если вам нужно в)
  • все остальные потоки (или основной процесс, если вы не используете потоки иначе) просто читает глобальную переменную

Язык C не гарантирует, что вы можете прочитать значение метки времени, если оно больше, чем sig_atomic_t. Вы можете использовать блокировку, чтобы справиться с этим, но блокировка тяжелая. Вместо этого вы можете использовать типизированную переменную volatile sig_atomic_t для индексации массива временных меток: фоновый поток обновляет следующий элемент в массиве, а затем обновляет индекс. Другие потоки читают индекс, а затем читают массив: они могут получить небольшую устаревшую временную метку (но в следующий раз они получат право), но они не сталкиваются с проблемой, когда они читают метку времени в в то же время он обновляется и получает несколько байтов старого значения и некоторого нового значения.

Но все это сильно переполняет только сотни сообщений в секунду.

Ответ 4

Вам нужна миллисекундная точность? Если нет, вы можете просто использовать time() и обрабатывать временную метку unix.

Ответ 5

Ниже приведен пример. Я вижу около 30 нс. printTime() from rashad Как получить текущее время и дату на С++?

#include <string>
#include <iostream>
#include <sys/time.h>
using namespace std;

void printTime(time_t now)
{
    struct tm  tstruct;
    char       buf[80];
    tstruct = *localtime(&now);
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);
    cout << buf << endl;
}

int main()
{
   timeval tv;
   time_t tm;

   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);
   for(int i=0; i<100000000; i++)
        gettimeofday(&tv,NULL);
   gettimeofday(&tv,NULL);
   printTime((time_t)tv.tv_sec);

   printTime(time(NULL));
   for(int i=0; i<100000000; i++)
        tm=time(NULL);
   printTime(time(NULL));

   return 0;
}

3 с для 100 000 000 вызовов или 30 нс;

2014-03-20.09:23:35
2014-03-20.09:23:38
2014-03-20.09:23:38
2014-03-20.09:23:41