Функция таймера для предоставления времени в нано секундах с использованием C ++ - PullRequest
100 голосов
/ 08 ноября 2008

Я хочу рассчитать время, необходимое API для возврата значения. Время, необходимое для такого действия, находится в интервале наносекунд. Поскольку API является классом / функцией C ++, я использую timer.h для расчета того же:

  #include <ctime>
  #include <cstdio>

  using namespace std;

  int main(int argc, char** argv) {

      clock_t start;
      double diff;
      start = clock();
      diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
      cout<<"printf: "<< diff <<'\n';

      return 0;
  }

Приведенный выше код дает время в секундах. Как получить то же самое за нано секунды и с большей точностью?

Ответы [ 16 ]

81 голосов
/ 09 ноября 2008

То, что другие опубликовали о повторном запуске функции в цикле, является правильным.

Для Linux (и BSD) вы хотите использовать clock_gettime () .

#include <sys/time.h>

int main()
{
   timespec ts;
   // clock_gettime(CLOCK_MONOTONIC, &ts); // Works on FreeBSD
   clock_gettime(CLOCK_REALTIME, &ts); // Works on Linux
}

Для окон вы хотите использовать QueryPerformanceCounter . А вот еще на QPC

Очевидно, существует известная проблема 1015 * с QPC на некоторых чипсетах, поэтому вы можете убедиться, что у вас нет этих чипсетов. Кроме того, некоторые двухъядерные процессоры AMD могут также вызвать проблему . Смотрите второй пост от sebbbi, где он утверждает:

QueryPerformanceCounter () и QueryPerformanceFrequency () предлагает немного лучшее разрешение, но есть разные вопросы. Например в Windows XP, все AMD Athlon X2 двойной основные процессоры возвращают ПК любого из ядра "случайно" (ПК иногда прыгает немного назад), если вы специально установить двухъядерный драйвер AMD пакет для решения проблемы. У нас нет заметил любые другие двухъядерные процессоры с похожими проблемами (p4 dual, p4 ht, Core2 Dual, Core2 Quad, Phenom Quad).

РЕДАКТИРОВАТЬ 2013/07/16:

Похоже, что существует определенное противоречие по эффективности QPC при определенных обстоятельствах, как указано в http://msdn.microsoft.com/en-us/library/windows/desktop/ee417693(v=vs.85).aspx

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

Однако в этом ответе StackOverflow https://stackoverflow.com/a/4588605/34329 говорится, что QPC должен нормально работать на любой ОС MS после Win XP с пакетом обновления 2.

В этой статье показано, что Windows 7 может определить, есть ли у процессора (ов) инвариантный TSC, и откатится к внешнему таймеру, если они этого не делают. http://performancebydesign.blogspot.com/2012/03/high-resolution-clocks-and-timers-for.html Синхронизация между процессорами все еще остается проблемой.

Прочие мелкие показания, связанные с таймерами:

См. Комментарии для более подробной информации.

68 голосов
/ 14 июля 2012

Этот новый ответ использует средство C ++ 11 <chrono>. В то время как есть другие ответы, которые показывают, как использовать <chrono>, ни один из них не показывает, как использовать <chrono> со средством RDTSC, упомянутым в нескольких других ответах здесь. Поэтому я подумал, что покажу, как использовать RDTSC с <chrono>. Кроме того, я покажу, как вы можете шаблонизировать тестовый код на часах, чтобы вы могли быстро переключаться между RDTSC и встроенными средствами синхронизации вашей системы (которые, вероятно, будут основаны на clock(), clock_gettime() и / или QueryPerformanceCounter.

Обратите внимание, что инструкция RDTSC специфична для x86. QueryPerformanceCounter только для Windows. И clock_gettime() только для POSIX. Ниже я представлю два новых тактовых генератора: std::chrono::high_resolution_clock и std::chrono::system_clock, которые, если вы можете предположить C ++ 11, теперь кроссплатформенные.

Во-первых, вот как вы создаете C ++ 11-совместимые часы из инструкции по сборке Intel rdtsc. Я назову это x::clock:

#include <chrono>

namespace x
{

struct clock
{
    typedef unsigned long long                 rep;
    typedef std::ratio<1, 2'800'000'000>       period; // My machine is 2.8 GHz
    typedef std::chrono::duration<rep, period> duration;
    typedef std::chrono::time_point<clock>     time_point;
    static const bool is_steady =              true;

    static time_point now() noexcept
    {
        unsigned lo, hi;
        asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
        return time_point(duration(static_cast<rep>(hi) << 32 | lo));
    }
};

}  // x

Все эти часы - подсчитывают циклы ЦП и сохраняют его в беззнаковом 64-разрядном целом числе. Возможно, вам придется настроить синтаксис ассемблера для вашего компилятора. Или ваш компилятор может предложить встроенную функцию, которую вы можете использовать вместо этого (например, now() {return __rdtsc();}).

Чтобы построить часы, вы должны дать им представление (тип хранилища). Вы также должны указать период времени, который должен быть постоянной времени компиляции, даже если ваша машина может изменять тактовую частоту в разных режимах питания. И из них вы можете легко определить «родную» продолжительность и время ваших часов в терминах этих основ.

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

Ниже приведен пример кода, который показывает, как использовать x::clock. На самом деле я шаблонный код на часах, поскольку я хотел бы показать, как вы можете использовать много разных часов с одинаковым синтаксисом. Этот конкретный тест показывает, какова нагрузка на цикл при выполнении того, что вы хотите использовать под циклом:

#include <iostream>

template <class clock>
void
test_empty_loop()
{
    // Define real time units
    typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
    // or:
    // typedef std::chrono::nanoseconds nanoseconds;
    // Define double-based unit of clock tick
    typedef std::chrono::duration<double, typename clock::period> Cycle;
    using std::chrono::duration_cast;
    const int N = 100000000;
    // Do it
    auto t0 = clock::now();
    for (int j = 0; j < N; ++j)
        asm volatile("");
    auto t1 = clock::now();
    // Get the clock ticks per iteration
    auto ticks_per_iter = Cycle(t1-t0)/N;
    std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
    // Convert to real time units
    std::cout << duration_cast<picoseconds>(ticks_per_iter).count()
              << "ps per iteration\n";
}

Первое, что делает этот код, это создает модуль «в реальном времени» для отображения результатов. Я выбрал пикосекунды, но вы можете выбрать любые единицы измерения, которые вам нравятся, на основе целых или с плавающей запятой. В качестве примера можно привести готовый блок std::chrono::nanoseconds, который я мог бы использовать.

В качестве другого примера я хочу напечатать среднее число тактов на одну итерацию в виде числа с плавающей запятой, поэтому я создаю другую длительность, основанную на double, которая имеет те же единицы измерения, что и такт часов (называемый Cycle в код).

Цикл рассчитан с вызовами на clock::now() с обеих сторон. Если вы хотите назвать тип, возвращаемый из этой функции, это:

typename clock::time_point t0 = clock::now();

(как ясно показано в примере x::clock, и также верно для системных часов).

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

Вы можете получить счет в любой продолжительности, используя функцию-член count(). Это возвращает внутреннее представление. Наконец, я использую std::chrono::duration_cast, чтобы преобразовать длительность Cycle в длительность picoseconds и распечатать ее.

Использовать этот код просто:

int main()
{
    std::cout << "\nUsing rdtsc:\n";
    test_empty_loop<x::clock>();

    std::cout << "\nUsing std::chrono::high_resolution_clock:\n";
    test_empty_loop<std::chrono::high_resolution_clock>();

    std::cout << "\nUsing std::chrono::system_clock:\n";
    test_empty_loop<std::chrono::system_clock>();
}

Выше я выполнил тест с использованием нашего домашнего x::clock и сравнил эти результаты с использованием двух системных часов: std::chrono::high_resolution_clock и std::chrono::system_clock. Для меня это печатает:

Using rdtsc:
1.72632 clock ticks per iteration
616ps per iteration

Using std::chrono::high_resolution_clock:
0.620105 clock ticks per iteration
620ps per iteration

Using std::chrono::system_clock:
0.00062457 clock ticks per iteration
624ps per iteration

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

Обратите внимание, что мой код полностью свободен от "магических констант преобразования". Действительно, во всем примере есть только два магических числа:

  1. Тактовая частота моей машины для определения x::clock.
  2. Количество итераций для проверки. Если изменение этого числа приводит к значительным различиям в результатах, то, вероятно, следует увеличить количество итераций или освободить компьютер от конкурирующих процессов во время тестирования.
27 голосов
/ 08 ноября 2008

При таком уровне точности было бы лучше рассуждать о тике ЦП, а не о системном вызове , например, clock () . И не забывайте, что если для выполнения инструкции требуется более одной наносекунды ... иметь точность наносекунды практически невозможно.

Тем не менее, что-то подобное - это начало:

Вот фактический код для получения количества тактовых импульсов процессора 80x86, прошедших с момента последнего запуска процессора. Он будет работать на Pentium и выше (386/486 не поддерживается). Этот код на самом деле специфичен для MS Visual C ++, но может быть очень легко перенесен на что-либо еще, если он поддерживает встроенную сборку.

inline __int64 GetCpuClocks()
{

    // Counter
    struct { int32 low, high; } counter;

    // Use RDTSC instruction to get clocks count
    __asm push EAX
    __asm push EDX
    __asm __emit 0fh __asm __emit 031h // RDTSC
    __asm mov counter.low, EAX
    __asm mov counter.high, EDX
    __asm pop EDX
    __asm pop EAX

    // Return result
    return *(__int64 *)(&counter);

}

Преимущество этой функции в том, что она чрезвычайно быстрая - для ее выполнения обычно требуется не более 50 циклов процессора.

Использование временных диаграмм :
Если вам нужно перевести счетчик часов в истинное истекшее время, разделите результаты на тактовую частоту вашего чипа. Помните, что «номинальная» ГГц может немного отличаться от фактической скорости вашего чипа. Чтобы проверить истинную скорость вашего чипа, вы можете использовать несколько очень хороших утилит или вызов Win32, QueryPerformanceFrequency ().

23 голосов
/ 08 июля 2010

Чтобы сделать это правильно, вы можете использовать один из двух способов: либо с RDTSC, либо с clock_gettime(). Второй примерно в 2 раза быстрее и имеет преимущество в том, что дает правильное абсолютное время. Обратите внимание, что для правильной работы RDTSC необходимо использовать его, как указано (другие комментарии на этой странице содержат ошибки и могут давать неправильные значения синхронизации на некоторых процессорах)

inline uint64_t rdtsc()
{
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx" );
    return (uint64_t)hi << 32 | lo;
}

и для clock_gettime: (я выбрал микросекундное разрешение произвольно)

#include <time.h>
#include <sys/timeb.h>
// needs -lrt (real-time lib)
// 1970-01-01 epoch UTC time, 1 mcs resolution (divide by 1M to get time_t)
uint64_t ClockGetTime()
{
    timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    return (uint64_t)ts.tv_sec * 1000000LL + (uint64_t)ts.tv_nsec / 1000LL;
}

сроки и полученные значения:

Absolute values:
rdtsc           = 4571567254267600
clock_gettime   = 1278605535506855

Processing time: (10000000 runs)
rdtsc           = 2292547353
clock_gettime   = 1031119636
21 голосов
/ 12 ноября 2008

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

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

int main (int argc, char** argv)
{
    // reset the clock
    timespec tS;
    tS.tv_sec = 0;
    tS.tv_nsec = 0;
    clock_settime(CLOCK_PROCESS_CPUTIME_ID, &tS);
    ...
    ... <code to check for the time to be put here>
    ...
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tS);
    cout << "Time taken is: " << tS.tv_sec << " " << tS.tv_nsec << endl;

    return 0;
}
8 голосов
/ 20 октября 2013

Для C ++ 11 , вот простая оболочка:

#include <iostream>
#include <chrono>

class Timer
{
public:
    Timer() : beg_(clock_::now()) {}
    void reset() { beg_ = clock_::now(); }
    double elapsed() const {
        return std::chrono::duration_cast<second_>
            (clock_::now() - beg_).count(); }

private:
    typedef std::chrono::high_resolution_clock clock_;
    typedef std::chrono::duration<double, std::ratio<1> > second_;
    std::chrono::time_point<clock_> beg_;
};

Или для C ++ 03 на * nix,

class Timer
{
public:
    Timer() { clock_gettime(CLOCK_REALTIME, &beg_); }

    double elapsed() {
        clock_gettime(CLOCK_REALTIME, &end_);
        return end_.tv_sec - beg_.tv_sec +
            (end_.tv_nsec - beg_.tv_nsec) / 1000000000.;
    }

    void reset() { clock_gettime(CLOCK_REALTIME, &beg_); }

private:
    timespec beg_, end_;
};

Пример использования:

int main()
{
    Timer tmr;
    double t = tmr.elapsed();
    std::cout << t << std::endl;

    tmr.reset();
    t = tmr.elapsed();
    std::cout << t << std::endl;
    return 0;
}

С https://gist.github.com/gongzhitaao/7062087

5 голосов
/ 09 ноября 2008

Вы можете использовать следующую функцию с gcc, работающим на процессорах x86:

unsigned long long rdtsc()
{
  #define rdtsc(low, high) \
         __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))

  unsigned int low, high;
  rdtsc(low, high);
  return ((ulonglong)high << 32) | low;
}

с Digital Mars C ++:

unsigned long long rdtsc()
{
   _asm
   {
        rdtsc
   }
}

, который считывает высокопроизводительный таймер на чипе. Я использую это при профилировании.

5 голосов
/ 08 ноября 2008

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

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

3 голосов
/ 09 ноября 2012

Вы можете использовать Embedded Profiler (бесплатно для Windows и Linux), который имеет интерфейс к многоплатформенному таймеру (в количестве циклов процессора) и может давать вам количество циклов в секунду:

EProfilerTimer timer;
timer.Start();

... // Your code here

const uint64_t number_of_elapsed_cycles = timer.Stop();
const uint64_t nano_seconds_elapsed =
    mumber_of_elapsed_cycles / (double) timer.GetCyclesPerSecond() * 1000000000;

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

3 голосов
/ 28 июня 2012

Используя метод Брока Адамса, с простым классом:

int get_cpu_ticks()
{
    LARGE_INTEGER ticks;
    QueryPerformanceFrequency(&ticks);
    return ticks.LowPart;
}

__int64 get_cpu_clocks()
{
    struct { int32 low, high; } counter;

    __asm cpuid
    __asm push EDX
    __asm rdtsc
    __asm mov counter.low, EAX
    __asm mov counter.high, EDX
    __asm pop EDX
    __asm pop EAX

    return *(__int64 *)(&counter);
}

class cbench
{
public:
    cbench(const char *desc_in) 
         : desc(strdup(desc_in)), start(get_cpu_clocks()) { }
    ~cbench()
    {
        printf("%s took: %.4f ms\n", desc, (float)(get_cpu_clocks()-start)/get_cpu_ticks());
        if(desc) free(desc);
    }
private:
    char *desc;
    __int64 start;
};

Пример использования:

int main()
{
    {
        cbench c("test");
        ... code ...
    }
    return 0;
}

Результат:

испытание заняло: 0,0002 мс

Имеет некоторые накладные расходы при вызове функции, но должен быть более чем достаточно быстрым:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...