C ++跨平台高分辨率计时器


73

我正在寻找在C ++中实现一种简单的计时器机制。该代码应可在Windows和Linux中使用。分辨率应尽可能精确(至少毫秒精度)。这将用于简单地跟踪时间的流逝,而不用于实现任何类型的事件驱动设计。什么是完成此任务的最佳工具?


3
更加详细一些。您是在计时函数调用还是要在指定的时间段后接收某种信号?这些都是“简单”的计时器应用程序,但是实现方式却大不相同。注意,在引号中使用“简单”:通用计算机中的计时绝不是“简单”的。
jmucchiello

Answers:


41

对于C ++ 03

Boost.Timer可能可以工作,但是取决于C函数clock,因此可能无法为您提供足够好的分辨率。

Boost.Date_Time包含之前在Stack Overflow上推荐的ptime。看到它的文档上microsec_clock::local_timemicrosec_clock::universal_time,但要注意它告诫说,“Win32系统往往不通过这个API达到微秒级的分辨率。”

STLsoft除其他外,提供围绕OS专用API的跨平台(Windows和Linux / Unix)瘦C ++包装器。它的性能库具有几个可以满足您需求的类。(要使其跨平台,请选择和名称空间performance_counter中都存在的类,然后使用与您的平台匹配的任何名称空间。)winstlunixstl

对于C ++ 11及更高版本

std::chrono库具有内置的此功能。有关详细信息,请参见@HowardHinnant的此答案


7
由于这是一个著名的问题/答案,因此更新可能很棒。具体而言,这可以使用现代C ++功能(例如<chrono>和)以标准且可移植的方式实现<thread>。如果可能的话,怎么办?
Manu343726

148

更新了旧问题的答案:

在C ++ 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”是扩展以缓解这些新类型的I / O问题,可在此处免费获得。

还有一个<chrono>可用的boost实现(可能仍在大罐中,不确定是否已发布)。

更新资料

这是对Ben在下面的评论的回应,即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硬件上尝试使用高分辨率时钟,那将比这更好。尽管已预先警告,但当今的时钟速度可以动态更改(它们实际上不是编译时常数)。使用多处理器计算机,您甚至可以从不同的处理器获取时间戳。但是,仍然可以在我的硬件上进行实验。如果您坚持使用毫秒级分辨率,这可能是一种解决方法。

此时钟的持续时间取决于CPU的时钟速度(如您所报告的)。即对我来说,这个时钟每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);

该转换将截断cpu循环的分数以形成纳秒。其他舍入模式也是可能的,但这是一个不同的主题。

对我来说,这将返回一个低至18个时钟滴答的持续时间,截断为6纳秒。

我在上述时钟上添加了一些“不变检查”,其中最重要的是检查clock::period该机器是否正确。同样,这不是可移植的代码,但是如果您使用此时钟,那么您已经承诺了。get_clock_speed()此处显示的私有函数获得OS X上的最大cpu频率,并且该数量应与的常数分母相同clock::period

将此代码移植到新计算机上时,添加此功能将为您节省一些调试时间,而无需将其更新为clock::period新计算机的速度。所有检查都在编译时或程序启动时完成。因此,它clock::now()至少不会影响性能。


2
high_resolution_clock不幸的是,在Visual Studio 11中,最短的非零间隔是几毫秒。
彼得(Petter)2012年

5
我花了几秒钟的时间……在一个时钟速度只有几分之一秒的平台上,数百万个纳秒。哇!!!我希望看到可以测量几分之一秒的平台。我认为几十纳秒的结果并不令人印象深刻。
Howard Hinnant 2012年

1
有谁知道在编译时获取cpu频率的方法吗?另外...这些天,cpu频率在运行时间中是否可以随Turbo模式而变化?也许这会使这种方法无效?我确实在VS11中需要一个不错的计时器,嗯。
大卫,

3
@戴夫:是的,CPU频率可以动态变化(我在答案中表示了这一点)。使用此功能时,我的实验通常会围绕我要测量的内容紧密结合。至少对于我的平台而言,这样的紧密循环通常会将cpu频率提高到最大值,并且该最大值通常是编译时常数(从cpu规范中读取)。因此,对于这种基准测试,这可能是一种有效的技术。但这显然不是通用的东西。我不建议运输。仅用于调查目的。
霍华德·辛南特

7
我使用VS2017在Windows上获得600-1200纳秒,它似乎正在使用高性能计时器。因此看来,这个1ms分辨率的问题不再是问题。
Programmdude

6

Matthew WilsonSTLSoft库提供了几种计时器类型,它们具有一致的接口,因此您可以即插即用。这些产品中包括低成本但低分辨率的计时器,以及高分辨率但具有高成本的计时器。还有一些用于测量线程前时间和每个进程时间的工具,以及所有用于测量经过时间的工具。

几年前,在Dobb博士的文章中有一篇详尽的文章涵盖了该内容,尽管它只涵盖了WinSTL子项目中定义的Windows。STLSoft在UNIXSTL子项目中还提供UNIX计时器,您可以使用“ PlatformSTL”,其中包括相应的UNIX或Windows,如:

#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

我已经看到了几次作为内部封闭式内部解决方案实现的方法....#ifdef一方面,它们全部依靠本地Windows Hi-res计时器,另一方面使用struct timeval(请参阅参考资料)使用Linux内核计时器man timeradd

您可以将其抽象化,并且一些开放源代码项目已经做到了-我查看的最后一个项目是CoinOR类CoinTimer,但是肯定有更多项目。


我决定走这条路。您的链接已死,因此我评论了一个仍在工作的链接:songho.ca/misc/timer/timer.html
Patrick

啊,没有什么比对一个已有八年历史的问题的评论了:)同时,我还很高兴使用Google的CCTZ库,它基于一些较新的C ++ 11习惯用法。
Dirk Eddelbuettel

4

我强烈建议为此使用boost :: posix_time库。我相信它支持各种分辨率的计时器,分辨率低至微秒


3

SDL2 具有出色的跨平台高分辨率计时器。但是,如果您需要毫秒以下的精度,我在这里编写了一个非常小的跨平台计时器库。它与C ++ 03和C ++ 11 / C ++更高版本兼容。





0

如果在项目中使用Qt框架,最好的解决方案可能是使用QElapsedTimer。


0

在这里参加聚会很晚,但是我正在使用无法升级到c ++ 11的旧代码库。我们团队中没有人精通c ++,因此添加像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 (song.ahn@gmail.com) - 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 (song.ahn@gmail.com) - 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

0

我发现这看起来很有希望,而且非常简单,不确定是否有任何缺点:

https://gist.github.com/ForeverZer0/0a4f80fc02b96e19380ebb7a3debbee5

/* ----------------------------------------------------------------------- */
/*
Easy embeddable cross-platform high resolution timer function. For each 
platform we select the high resolution timer. You can call the 'ns()' 
function in your file after embedding this. 
*/
#include <stdint.h>
#if defined(__linux)
#  define HAVE_POSIX_TIMER
#  include <time.h>
#  ifdef CLOCK_MONOTONIC
#     define CLOCKID CLOCK_MONOTONIC
#  else
#     define CLOCKID CLOCK_REALTIME
#  endif
#elif defined(__APPLE__)
#  define HAVE_MACH_TIMER
#  include <mach/mach_time.h>
#elif defined(_WIN32)
#  define WIN32_LEAN_AND_MEAN
#  include <windows.h>
#endif
static uint64_t ns() {
static uint64_t is_init = 0;
#if defined(__APPLE__)
    static mach_timebase_info_data_t info;
    if (0 == is_init) {
        mach_timebase_info(&info);
        is_init = 1;
    }
    uint64_t now;
    now = mach_absolute_time();
    now *= info.numer;
    now /= info.denom;
    return now;
#elif defined(__linux)
    static struct timespec linux_rate;
    if (0 == is_init) {
        clock_getres(CLOCKID, &linux_rate);
        is_init = 1;
    }
    uint64_t now;
    struct timespec spec;
    clock_gettime(CLOCKID, &spec);
    now = spec.tv_sec * 1.0e9 + spec.tv_nsec;
    return now;
#elif defined(_WIN32)
    static LARGE_INTEGER win_frequency;
    if (0 == is_init) {
        QueryPerformanceFrequency(&win_frequency);
        is_init = 1;
    }
    LARGE_INTEGER now;
    QueryPerformanceCounter(&now);
    return (uint64_t) ((1e9 * now.QuadPart)  / win_frequency.QuadPart);
#endif
}
/* ----------------------------------------------------------------------- */-------------------------------- */
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.