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

85

我希望在C++中实现一个简单的计时器机制。代码应该能在Windows和Linux上运行。计时器的精度应该尽可能高(至少精确到毫秒级别)。这将用于简单跟踪时间的流逝,而不是实现任何形式的事件驱动设计。最佳工具是什么?


5
请具体说明一下,您是要计时一个函数调用还是希望在指定时间后收到某种信号。这两个都是“简单”的计时器应用,但它们的实现方式非常不同。请注意,“简单”用引号括起来:在通用计算机中进行计时从来都不是“简单”的。 - jmucchiello
C版本 https://dev59.com/jHRC5IYBdhLWcg3wP-n5 - Ciro Santilli OurBigBook.com
从CPU的角度来看,(至少毫秒级的精度)并不是很高的分辨率。 - undefined
14个回答

164

针对一个旧问题的更新回答:

在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问题,并可在此处免费获取。

boost库中也有一个实现了<chrono>的版本(可能仍在开发中,不确定是否已发布)。

更新

这是对Ben下面的评论做出的回应,他指出在VS11中后续对std::chrono::high_resolution_clock的调用需要几毫秒的时间。以下是一个<chrono>兼容的解决方案。但是它只适用于英特尔硬件,您需要深入了解内联汇编(与编译器相关的语法),并且必须将机器的时钟速度硬编码到时钟中:

#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();

所以它不是可移植的。但如果你想在自己的英特尔硬件上尝试使用高分辨率时钟,那么它就再好不过了。不过要注意,今天的时钟速度可以动态更改(它们并不是编译时常量)。而且在多处理器机器上,甚至可以从不同的处理器获取时间戳。但是,在我的硬件上进行实验效果还是不错的。如果你只能获得毫秒级分辨率,那么这可能是一个解决方法。

该时钟的持续时间以您 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
在Visual Studio 11中,high_resolution_clock的最短非零间隔是几毫秒,这是不幸的。 - Petter
6
我需要几秒钟才能理解...在时钟速度不到一纳秒的平台上,这相当于数百万个纳秒。哇!我希望能看到可以测量纳秒级别的平台。我认为我的结果几十纳秒并不那么令人印象深刻。 - Howard Hinnant
3
有没有方法在编译时获取CPU频率?此外,随着 Turbo 模式等技术的出现,CPU 频率现在是否会在运行时发生变化?或许这会使这种方法失效?不过,我确实需要一个好用的计时器来用于 VS11。 - David
3
@Dave:是的,CPU频率可以动态变化(我在回答中已经说明了这一点)。当我使用它时,我的实验通常围绕着我试图测量的某个东西进行一个紧密循环。至少对于我的平台而言,这样一个紧密循环通常会将CPU频率提升到最大值,并且该最大值通常是编译时常量(从CPU规格书上读取)。因此,对于这种基准测试,这可以是一种有效的技术。但显然,这不是通用用途的东西。我不建议将其发布。只是为了调查目的而使用。 - Howard Hinnant
7
在Windows平台上使用VS2017,我得到了600-1200纳秒的计时结果,且似乎是使用了高性能定时器。因此,1毫秒分辨率的问题似乎不再存在。 - Programmdude
显示剩余15条评论

44

对于C++03:

Boost.Timer可能适用,但它依赖于C函数clock,因此可能无法具有足够的分辨率。

Boost.Date_Time包括ptime,在Stack Overflow上曾经推荐过。请查看其关于microsec_clock::local_timemicrosec_clock::universal_time的文档,但请注意其警告:"Win32系统通常无法通过此API实现微秒分辨率。"

STLsoft提供了许多内容,其中包括围绕特定操作系统API的跨平台(Windows和Linux / Unix)C ++封装。其performance library有几个类可以满足您的需求。(为了使其跨平台,请选择像performance_counter这样的类,该类存在于winstlunixstl名称空间中,然后使用与您的平台匹配的名称空间。)

对于C++11及以上版本:

std::chrono库内置了这个功能。请参见@HowardHinnant的这个答案以了解详情。


7
因为这是一个著名的问题/答案,更新一下会很好。具体而言,可以使用现代 C++ 特性(如 <chrono><thread>)以标准且可移植的方式来实现这一点吗?如果可能的话,怎么做? - Manu343726

6

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

多年前,Dr. Dobb's上的一篇详尽文章介绍了它,虽然只涵盖了Windows的计时器,这些计时器定义在WinSTL子项目中。STLSoft还为UNIX计时器提供了UNIXSTL子项目,并且您可以使用"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;
}

HTH


5

StlSoft开源库在Windows和Linux平台上都提供了一个相当好的计时器。如果你想自己实现它,只需要查看他们的源代码。


5

4
我曾多次见到这种实现作为闭源内部解决方案......所有这些方案都采用了一方面基于本地 Windows 高分辨率计时器的 #ifdef 解决方案,另一方面则使用 Linux 内核计时器,并使用 struct timeval(参见 man timeradd)。
你可以进行抽象化,一些开源项目已经做到了--我最近看过的一个是CoinOR 类 CoinTimer,但肯定还有更多类似的项目。

我决定走这条路线。你的链接已经失效了,所以我留下了一个仍然有效的链接:http://www.songho.ca/misc/timer/timer.html - Patrick
啊,没有什么比对一个八年前的问题进行评论更让人愉快了 :) 在此期间,我使用谷歌的CCTZ库运气不错,该库基于一些更新的C++11习惯用法构建。 - Dirk Eddelbuettel

4

我强烈推荐使用 boost::posix_time 库。它支持定时器的多种分辨率,甚至可以达到微秒级别。


3

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


3
我找到了这个看起来很有前途的东西,非常简单明了,不确定是否存在任何缺点: 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
}
/* ----------------------------------------------------------------------- */-------------------------------- */

2

对于C++库相关的问题,通常的答案是使用BOOST:http://www.boost.org/doc/libs/1_40_0/libs/timer/timer.htm。这个库是否满足你的需求呢?可能不太符合,但至少可以作为一个起点。

问题在于你需要一个可移植的计时器函数,但这种函数在各种操作系统中并不普遍存在。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接