С++ кросс-платформенный таймер с высоким разрешением

Я ищу реализовать простой механизм таймера в С++. Код должен работать в Windows и Linux. Разрешение должно быть максимально точным (точность не менее миллисекунды). Это будет использоваться для простого отслеживания времени, а не для реализации какого-либо проекта, управляемого событиями. Какой лучший инструмент для этого?

Ответы

Ответ 1

Для С++ 03:

Boost.Timer может работать, но это зависит от функции C clock и поэтому может не иметь достаточно хорошего разрешения для вас.

Boost.Date_Time включает ptime класс, который ранее был рекомендован для. См. Его документы на microsec_clock::local_time и microsec_clock::universal_time, но обратите внимание на его предостережение о том, что "системы Win32 часто не достигают разрешения по микросекундам через этот API".

STLsoft предоставляет, среди прочего, тонкие кросс-платформенные (Windows и Linux/Unix) С++-обертки вокруг API-интерфейсов, специфичных для ОС, Его библиотека имеет несколько классов, которые будут делать то, что вам нужно. (Чтобы сделать его кросс-платформой, выберите класс типа performance_counter, который существует в пространствах имен winstl и unixstl, а затем используйте любое пространство имен, соответствующее вашей платформе.)

Для С++ 11 и выше:

В библиотеке std::chrono встроена эта функциональность. Подробнее см. этот ответ от @HowardHinnant.

Ответ 2

Обновленный ответ для старого вопроса:

В С++ 11 вы можете портативно получить таймер с наивысшим разрешением с помощью:

#include <iostream>
#include <chrono>
#include "chrono_io"

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    auto t1 = Clock::now();
    auto t2 = Clock::now();
    std::cout << t2-t1 << '\n';
}

Пример вывода:

74 nanoseconds

"chrono_io" - это расширение, облегчающее проблемы ввода-вывода с этими новыми типами и свободно доступное здесь.

Существует также реализация <chrono>, доступная в boost (может все еще быть на кончике ствола, не уверен, что она была выпущена).

Обновление

Это в ответ на комментарий Бена ниже, что последующие вызовы std::chrono::high_resolution_clock занимают несколько миллисекунд в VS11. Ниже приведено <chrono> -совместимое обходное решение. Однако он работает только на аппаратных средствах Intel, вам необходимо окунуться в встроенную сборку (синтаксис, который может измениться в зависимости от компилятора), и вам необходимо установить скорость часов в часах:

#include <chrono>

struct clock
{
    typedef unsigned long long                 rep;
    typedef std::ratio<1, 2800000000>          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));
    }

private:

    static
    unsigned
    get_clock_speed()
    {
        int mib[] = {CTL_HW, HW_CPU_FREQ};
        const std::size_t namelen = sizeof(mib)/sizeof(mib[0]);
        unsigned freq;
        size_t freq_len = sizeof(freq);
        if (sysctl(mib, namelen, &freq, &freq_len, nullptr, 0) != 0)
            return 0;
        return freq;
    }

    static
    bool
    check_invariants()
    {
        static_assert(1 == period::num, "period must be 1/freq");
        assert(get_clock_speed() == period::den);
        static_assert(std::is_same<rep, duration::rep>::value,
                      "rep and duration::rep must be the same type");
        static_assert(std::is_same<period, duration::period>::value,
                      "period and duration::period must be the same type");
        static_assert(std::is_same<duration, time_point::duration>::value,
                      "duration and time_point::duration must be the same type");
        return true;
    }

    static const bool invariants;
};

const bool clock::invariants = clock::check_invariants();

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

Эти часы имеют длительность с точки зрения тактовой частоты процессора (как вы сообщили об этом). То есть для меня эти часы гадают один раз каждые 1/2 800 000 000 секунд. Если вы хотите, вы можете преобразовать это в наносекунды (например) с помощью:

using std::chrono::nanoseconds;
using std::chrono::duration_cast;
auto t0 = clock::now();
auto t1 = clock::now();
nanoseconds ns = duration_cast<nanoseconds>(t1-t0);

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

Для меня это вернет длительность до 18 тактов, которая усекает до 6 наносекунд.

Я добавил некоторую "проверку инварианта" на вышеуказанные часы, наиболее важным из которых является проверка правильности clock::period для машины. Опять же, это не переносимый код, но если вы используете эти часы, вы уже это сделали. Частная функция get_clock_speed(), показанная здесь, получает максимальную частоту процессора в OS X и должна быть того же числа, что и константный знаменатель clock::period.

Добавление этого позволит вам немного времени отладки при переносе этого кода на новую машину и забыть обновить clock::period до скорости вашей новой машины. Вся проверка выполняется либо во время компиляции, либо во время запуска программы. Таким образом, это не повлияет на производительность clock::now() как минимум.

Ответ 3

Мэтью Уилсон Библиотеки STLSoft предоставляют несколько типов таймеров, с конгруэнтными интерфейсами, чтобы вы могли подключаться и играть. Среди предложений - таймеры, которые являются недорогими, но с низким разрешением, и те, которые имеют высокое разрешение, но имеют высокую стоимость. Существуют также измерения времени предварительной обработки и для измерения времени обработки, а также всех измеряемых истекших периодов.

Вот несколько лет назад была исчерпывающая статья

#include <platformstl/performance/performance_counter.hpp>
#include <iostream>

int main()
{
    platformstl::performance_counter c;

    c.start();
    for(int i = 0; i < 1000000000; ++i);
    c.stop();

    std::cout << "time (s): " << c.get_seconds() << std::endl;
    std::cout << "time (ms): " << c.get_milliseconds() << std::endl;
    std::cout << "time (us): " << c.get_microseconds() << std::endl;
}

НТН

Ответ 4

StlSoft библиотека с открытым исходным кодом обеспечивает довольно good таймер на обоих окнах и платформах Linux. Если вы хотите, чтобы он реализовывался самостоятельно, просто посмотрите на их источники.

Ответ 7

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

Ответ 8

Первый ответ на вопросы библиотеки С++, как правило, BOOST: http://www.boost.org/doc/libs/1_40_0/libs/timer/timer.htm. Делает ли это то, что вы хотите? Наверное, это не начало.

Проблема в том, что вы хотите, чтобы портативные и таймерные функции не были универсальными в ОС.

Ответ 9

Я видел, как это реализовано несколько раз в качестве внутренних решений с закрытыми исходными кодами.... которые прибегают к решениям #ifdef вокруг собственных таймеров hi-res для Windows, с одной стороны, и таймеров ядра Linux с использованием struct timeval (см. man timeradd), с другой стороны.

Вы можете абстрагироваться от этого, и несколько проектов с открытым исходным кодом сделали это - последний, на который я смотрел, был CoinOR class CoinTimer, но, безусловно, их больше.

Ответ 11

SDL2 имеет отличный кросс-платформенный таймер с высоким разрешением. Если вам нужна точность в миллисекундах, я написал небольшую кросс-платформенную библиотеку таймеров здесь. Он совместим как с С++ 03, так и с С++ 11/более поздними версиями С++.

Ответ 13

Если в проекте используется каркас Qt, лучшим решением является использование QElapsedTimer.

Ответ 14

Поздно к вечеринке здесь, но я работаю в устаревшей кодовой базе, которую нельзя обновить до С++ 11. Никто из нашей команды не имеет особого опыта работы на С++, поэтому добавление библиотеки, такой как STL, оказывается затруднительным (помимо потенциальных проблем, которые другие подняли о проблемах развертывания). Мне действительно нужен был чрезвычайно простой кросс-платформенный таймер, который мог бы жить сам по себе без чего-либо, кроме стандартных системных библиотек с голыми костями. Вот что я нашел:

http://www.songho.ca/misc/timer/timer.html

Перераспределение всего источника здесь просто так, чтобы он не терялся, если сайт когда-либо умирает:

    //////////////////////////////////////////////////////////////////////////////
// Timer.cpp
// =========
// High Resolution Timer.
// This timer is able to measure the elapsed time with 1 micro-second accuracy
// in both Windows, Linux and Unix system 
//
//  AUTHOR: Song Ho Ahn ([email protected]) - http://www.songho.ca/misc/timer/timer.html
// CREATED: 2003-01-13
// UPDATED: 2017-03-30
//
// Copyright (c) 2003 Song Ho Ahn
//////////////////////////////////////////////////////////////////////////////

#include "Timer.h"
#include <stdlib.h>

///////////////////////////////////////////////////////////////////////////////
// constructor
///////////////////////////////////////////////////////////////////////////////
Timer::Timer()
{
#if defined(WIN32) || defined(_WIN32)
    QueryPerformanceFrequency(&frequency);
    startCount.QuadPart = 0;
    endCount.QuadPart = 0;
#else
    startCount.tv_sec = startCount.tv_usec = 0;
    endCount.tv_sec = endCount.tv_usec = 0;
#endif

    stopped = 0;
    startTimeInMicroSec = 0;
    endTimeInMicroSec = 0;
}



///////////////////////////////////////////////////////////////////////////////
// distructor
///////////////////////////////////////////////////////////////////////////////
Timer::~Timer()
{
}



///////////////////////////////////////////////////////////////////////////////
// start timer.
// startCount will be set at this point.
///////////////////////////////////////////////////////////////////////////////
void Timer::start()
{
    stopped = 0; // reset stop flag
#if defined(WIN32) || defined(_WIN32)
    QueryPerformanceCounter(&startCount);
#else
    gettimeofday(&startCount, NULL);
#endif
}



///////////////////////////////////////////////////////////////////////////////
// stop the timer.
// endCount will be set at this point.
///////////////////////////////////////////////////////////////////////////////
void Timer::stop()
{
    stopped = 1; // set timer stopped flag

#if defined(WIN32) || defined(_WIN32)
    QueryPerformanceCounter(&endCount);
#else
    gettimeofday(&endCount, NULL);
#endif
}



///////////////////////////////////////////////////////////////////////////////
// compute elapsed time in micro-second resolution.
// other getElapsedTime will call this first, then convert to correspond resolution.
///////////////////////////////////////////////////////////////////////////////
double Timer::getElapsedTimeInMicroSec()
{
#if defined(WIN32) || defined(_WIN32)
    if(!stopped)
        QueryPerformanceCounter(&endCount);

    startTimeInMicroSec = startCount.QuadPart * (1000000.0 / frequency.QuadPart);
    endTimeInMicroSec = endCount.QuadPart * (1000000.0 / frequency.QuadPart);
#else
    if(!stopped)
        gettimeofday(&endCount, NULL);

    startTimeInMicroSec = (startCount.tv_sec * 1000000.0) + startCount.tv_usec;
    endTimeInMicroSec = (endCount.tv_sec * 1000000.0) + endCount.tv_usec;
#endif

    return endTimeInMicroSec - startTimeInMicroSec;
}



///////////////////////////////////////////////////////////////////////////////
// divide elapsedTimeInMicroSec by 1000
///////////////////////////////////////////////////////////////////////////////
double Timer::getElapsedTimeInMilliSec()
{
    return this->getElapsedTimeInMicroSec() * 0.001;
}



///////////////////////////////////////////////////////////////////////////////
// divide elapsedTimeInMicroSec by 1000000
///////////////////////////////////////////////////////////////////////////////
double Timer::getElapsedTimeInSec()
{
    return this->getElapsedTimeInMicroSec() * 0.000001;
}



///////////////////////////////////////////////////////////////////////////////
// same as getElapsedTimeInSec()
///////////////////////////////////////////////////////////////////////////////
double Timer::getElapsedTime()
{
    return this->getElapsedTimeInSec();
}

и заголовочный файл:

//////////////////////////////////////////////////////////////////////////////
// Timer.h
// =======
// High Resolution Timer.
// This timer is able to measure the elapsed time with 1 micro-second accuracy
// in both Windows, Linux and Unix system 
//
//  AUTHOR: Song Ho Ahn ([email protected]) - http://www.songho.ca/misc/timer/timer.html
// CREATED: 2003-01-13
// UPDATED: 2017-03-30
//
// Copyright (c) 2003 Song Ho Ahn
//////////////////////////////////////////////////////////////////////////////

#ifndef TIMER_H_DEF
#define TIMER_H_DEF

#if defined(WIN32) || defined(_WIN32)   // Windows system specific
#include <windows.h>
#else          // Unix based system specific
#include <sys/time.h>
#endif


class Timer
{
public:
    Timer();                                    // default constructor
    ~Timer();                                   // default destructor

    void   start();                             // start timer
    void   stop();                              // stop the timer
    double getElapsedTime();                    // get elapsed time in second
    double getElapsedTimeInSec();               // get elapsed time in second (same as getElapsedTime)
    double getElapsedTimeInMilliSec();          // get elapsed time in milli-second
    double getElapsedTimeInMicroSec();          // get elapsed time in micro-second


protected:


private:
    double startTimeInMicroSec;                 // starting time in micro-second
    double endTimeInMicroSec;                   // ending time in micro-second
    int    stopped;                             // stop flag 
#if defined(WIN32) || defined(_WIN32)
    LARGE_INTEGER frequency;                    // ticks per second
    LARGE_INTEGER startCount;                   //
    LARGE_INTEGER endCount;                     //
#else
    timeval startCount;                         //
    timeval endCount;                           //
#endif
};

#endif // TIMER_H_DEF