Функция таймера для обеспечения времени в наносекундах с использованием С++

Я хочу рассчитать время, необходимое API для возврата значения. Время, затрачиваемое на такое действие, находится в пространстве nano секунд. Поскольку API является классом/функцией С++, я использую 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;
  }

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

Ответы

Ответ 1

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

Для 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

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

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

EDIT 2013/07/16:

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

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

Однако в этом ответе StackOverflow fooobar.com/questions/9686/... указано, что QPC должен отлично работать на любой ОС MS после пакета обновления 2 для Windows XP.

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

Другое точное чтение, относящееся к таймерам:

Подробнее см. комментарии.

Ответ 2

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

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

Во-первых, вот как вы создаете С++ 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

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

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

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

Ответ 3

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

Тем не менее что-то вроде этого - это начало:

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

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().

Ответ 4

Чтобы сделать это правильно, вы можете использовать один из двух способов: либо пойти с 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

Ответ 5

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

#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;
}

Ответ 6

Для С++ 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_;
};

Или для С++ 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

Ответ 7

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

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

Ответ 8

Вы можете использовать следующую функцию при работе 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 С++:

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

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

Ответ 9

Использование метода Брок Адамса с простым классом:

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 мс

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

Ответ 10

Если вам нужна субсекундная точность, вам нужно использовать системные расширения, и вам придется проверить документацию для операционной системы. POSIX поддерживает до микросекунд с gettimeofday, но не более точным, поскольку компьютеры не имели частот выше 1 ГГц.

Если вы используете Boost, вы можете проверить boost:: posix_time.

Ответ 11

Я использую код Borland, вот код ti_hund дает мне несколько раз отрицательное число, но время довольно хорошее.

#include <dos.h>

void main() 
{
struct  time t;
int Hour,Min,Sec,Hun;
gettime(&t);
Hour=t.ti_hour;
Min=t.ti_min;
Sec=t.ti_sec;
Hun=t.ti_hund;
printf("Start time is: %2d:%02d:%02d.%02d\n",
   t.ti_hour, t.ti_min, t.ti_sec, t.ti_hund);
....
your code to time
...

// read the time here remove Hours and min if the time is in sec

gettime(&t);
printf("\nTid Hour:%d Min:%d Sec:%d  Hundreds:%d\n",t.ti_hour-Hour,
                             t.ti_min-Min,t.ti_sec-Sec,t.ti_hund-Hun);
printf("\n\nAlt Ferdig Press a Key\n\n");
getch();
} // end main

Ответ 12

Вы можете использовать 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;

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

Ответ 13

Если это для Linux, я использую функцию gettimeofday, которая возвращает структуру, которая дает секунды и микросекунды со времен Эпохи. Затем вы можете использовать timersub для вычитания двух, чтобы получить разницу во времени, и преобразовать его в любую точность времени, которое вы хотите. Однако вы указываете наносекунды, и похоже, что функция clock_gettime() - это то, что вы ищете. Он помещает время в секундах и наносекундах в структуру, в которую вы проходите.

Ответ 14

Что вы думаете об этом:

    int iceu_system_GetTimeNow(long long int *res)
    {
      static struct timespec buffer;
      // 
    #ifdef __CYGWIN__
      if (clock_gettime(CLOCK_REALTIME, &buffer))
        return 1;
    #else
      if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &buffer))
        return 1;
    #endif
      *res=(long long int)buffer.tv_sec * 1000000000LL + (long long int)buffer.tv_nsec;
      return 0;
    }

Ответ 15

Вот хороший Boost таймер, который хорошо работает:

//Stopwatch.hpp

#ifndef STOPWATCH_HPP
#define STOPWATCH_HPP

//Boost
#include <boost/chrono.hpp>
//Std
#include <cstdint>

class Stopwatch
{
public:
    Stopwatch();
    virtual         ~Stopwatch();
    void            Restart();
    std::uint64_t   Get_elapsed_ns();
    std::uint64_t   Get_elapsed_us();
    std::uint64_t   Get_elapsed_ms();
    std::uint64_t   Get_elapsed_s();
private:
    boost::chrono::high_resolution_clock::time_point _start_time;
};

#endif // STOPWATCH_HPP


//Stopwatch.cpp

#include "Stopwatch.hpp"

Stopwatch::Stopwatch():
    _start_time(boost::chrono::high_resolution_clock::now()) {}

Stopwatch::~Stopwatch() {}

void Stopwatch::Restart()
{
    _start_time = boost::chrono::high_resolution_clock::now();
}

std::uint64_t Stopwatch::Get_elapsed_ns()
{
    boost::chrono::nanoseconds nano_s = boost::chrono::duration_cast<boost::chrono::nanoseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(nano_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_us()
{
    boost::chrono::microseconds micro_s = boost::chrono::duration_cast<boost::chrono::microseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(micro_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_ms()
{
    boost::chrono::milliseconds milli_s = boost::chrono::duration_cast<boost::chrono::milliseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(milli_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_s()
{
    boost::chrono::seconds sec = boost::chrono::duration_cast<boost::chrono::seconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(sec.count());
}

Ответ 16

Минималистичная копия и вставка-структура + ленивое использование

Если идея состоит в том, чтобы иметь минималистскую структуру, которую вы можете использовать для быстрых тестов, я предлагаю вам просто скопировать и вставить в любом месте вашего файла С++ сразу после #include. Это единственный случай, когда я жертвую форматированием в стиле Allman.

Вы можете легко настроить точность в первой строке структуры. Возможные значения: nanoseconds, microseconds, milliseconds, seconds, minutes или hours.

#include <chrono>
struct MeasureTime
{
    using precision = std::chrono::microseconds;
    std::vector<std::chrono::steady_clock::time_point> times;
    std::chrono::steady_clock::time_point oneLast;
    void p() {
        std::cout << "Mark " 
                << times.size()/2
                << ": " 
                << std::chrono::duration_cast<precision>(times.back() - oneLast).count() 
                << std::endl;
    }
    void m() {
        oneLast = times.back();
        times.push_back(std::chrono::steady_clock::now());
    }
    void t() {
        m();
        p();
        m();
    }
    MeasureTime() {
        times.push_back(std::chrono::steady_clock::now());
    }
};

Использование

MeasureTime m; // first time is already in memory
doFnc1();
m.t(); // Mark 1: next time, and print difference with previous mark
doFnc2();
m.t(); // Mark 2: next time, and print difference with previous mark
doStuff = doMoreStuff();
andDoItAgain = doStuff.aoeuaoeu();
m.t(); // prints 'Mark 3: 123123' etc...

Результат стандартного вывода

Mark 1: 123
Mark 2: 32
Mark 3: 433234

Если вам требуется сводка после выполнения

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

void s() { // summary
    int i = 0;
    std::chrono::steady_clock::time_point tprev;
    for(auto tcur : times)
    {
        if(i > 0)
        {
            std::cout << "Mark " << i << ": "
                    << std::chrono::duration_cast<precision>(tprev - tcur).count()
                    << std::endl;
        }
        tprev = tcur;
        ++i;
    }
}

Итак, вы можете просто использовать:

MeasureTime m;
doFnc1();
m.m();
doFnc2();
m.m();
doStuff = doMoreStuff();
andDoItAgain = doStuff.aoeuaoeu();
m.m();
m.s();

Будет отображаться все метки, как раньше, но затем после выполнения другого кода. Обратите внимание, что вы не должны использовать как m.s(), так и m.t().