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

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

5 ответов

Решение

Вы на самом деле оценили, и нашли gettimeofday быть недопустимо медленным?

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

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

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

Часы POSIX

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

  • время (с) => 3 цикла
  • ftime (мс) => 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 мешает делать метод кеша. Это может быть просто для того, чтобы обеспечить более предсказуемую производительность на разных платформах или улучшить масштабирование с использованием нескольких ядер. Это также не может быть полезной оптимизацией.

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

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

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

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

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

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

#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 секунды для 100000000 звонков или 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

Вам нужна точность в миллисекундах? Если бы не вы могли просто использовать time() и иметь дело с меткой времени Unix.

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