如何使用 ANSI C 测量以毫秒为单位的时间?

只使用 ANSI C,有没有方法可以精确到毫秒甚至更多?我在浏览 time.h 但只找到第二个精度函数。

256367 次浏览

没有一个 ANSI C 函数能够提供超过1秒钟的分辨率,但是 POSIX 函数 gettimeofday能够提供微秒级的分辨率。时钟函数只测量进程执行的时间,在许多系统上并不准确。

你可以这样使用这个函数:

struct timeval tval_before, tval_after, tval_result;


gettimeofday(&tval_before, NULL);


// Some code you want to time, for example:
sleep(1);


gettimeofday(&tval_after, NULL);


timersub(&tval_after, &tval_before, &tval_result);


printf("Time elapsed: %ld.%06ld\n", (long int)tval_result.tv_sec, (long int)tval_result.tv_usec);

这在我的机器上返回 Time elapsed: 1.000870

您可能获得的最佳精度是通过使用仅 x86的“ rdtsc”指令,该指令可以提供时钟级分辨率(当然,必须考虑 rdtsc 调用本身的成本,这可以在应用程序启动时轻松测量)。

这里的主要问题是测量每秒钟的时钟数量,这应该不会太难。

#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);

我总是使用 lock _ gettime ()函数,从 CLOCK _ MONOTONIC 时钟返回时间。返回的时间是以秒和纳秒为单位的时间量,因为过去有一些未指定的点,如新纪元的系统启动。

#include <stdio.h>
#include <stdint.h>
#include <time.h>


int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}


int main(int argc, char **argv)
{
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);


// Some code I am interested in measuring


clock_gettime(CLOCK_MONOTONIC, &end);


uint64_t timeElapsed = timespecDiff(&end, &start);
}

窗下:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);

来自 C11的 timespec_get

返回纳秒,四舍五入到实现的分辨率。

看起来像是 ANSI 剽窃了 POSIX 的 clock_gettime

例如: 在 Ubuntu 15.10上,printf每100毫秒完成一次:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


static long get_nanos(void) {
struct timespec ts;
timespec_get(&ts, TIME_UTC);
return (long)ts.tv_sec * 1000000000L + ts.tv_nsec;
}


int main(void) {
long nanos;
long last_nanos;
long start;
nanos = get_nanos();
last_nanos = nanos;
start = nanos;
while (1) {
nanos = get_nanos();
if (nanos - last_nanos > 100000000L) {
printf("current nanos: %ld\n", nanos - start);
last_nanos = nanos;
}
}
return EXIT_SUCCESS;
}

C11 N1570标准稿7.27.2.5“ timespec _ get 函数表示”:

如果 base 为 TIME _ UTC,则 tv _ sec 成员设置为从 实现定义的时代,截断为一个整数值,tv _ nsec 成员为 设置为纳秒的整数,四舍五入到系统时钟的分辨率

321)虽然结构时间规格对象以纳秒级分辨率描述时间,但可用的 分辨率是系统相关的,甚至可能大于1秒。

C + + 11也得到了 std::chrono::high_resolution_clock: 跨平台高分辨率定时器

Glibc 2.21的实现

sysdeps/posix/timespec_get.c下可以找到:

int
timespec_get (struct timespec *ts, int base)
{
switch (base)
{
case TIME_UTC:
if (__clock_gettime (CLOCK_REALTIME, ts) < 0)
return 0;
break;


default:
return 0;
}


return base;
}

很明显:

  • 目前只支持 TIME_UTC

  • 它转发到 __clock_gettime (CLOCK_REALTIME, ts),这是一个 POSIX API: 一个 ref = “ http://pubs.opengroupp.org/onlinepubs/9699919799/function/lock _ getres.html”rel = “ norefrer”> http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

    Linux x86-64有一个 clock_gettime系统调用。

    请注意,这不是一种万无一失的微基准测试方法,因为:

    • man clock_gettime说,如果您在程序运行时更改了某些系统时间设置,那么这个度量可能会有不连续性。这当然应该是一个罕见的事件,你可以忽略它。

    • 这衡量的是壁时间,所以如果调度程序决定忘记您的任务,它将看起来运行的时间更长。

    由于这些原因,getrusage()可能是一个更好的 POSIX 基准测试工具,尽管它的最大微秒精度较低。

    更多信息请访问: < a href = “ https://stackoverflow. com/questions/12392278/meter-time-in-Linux-getruse-vs-lock-gettime-vs-lock-vs-gettimeofday”> 在 Linux 中测量时间-时间 vs 时钟 vs getruse vs 时钟 _ gettime vs gettimeofday vs timespec _ get?

实现便携式解决方案

由于这里已经提到,没有适当的 ANSI 解决方案有足够的精度的时间测量问题,我想写的方法如何得到一个便携式,如果可能的话,一个高分辨率的时间测量解决方案。

单调时钟与时间戳

一般来说,有两种时间测量方法:

  • 单调时钟;
  • 当前(日期)时间戳。

第一种使用一个单调的时钟计数器(有时它被称为计数器) ,它以预定义的频率计数滴答声,所以如果你有一个滴答声的值和频率是已知的,你可以很容易地将滴答声转换为运行时间。它实际上并不能保证一个单调的时钟以任何方式反映当前系统的时间,它也可以计数自系统启动以来的滴答声。但是它保证了时钟总是以一种不断增长的方式运行,而不管系统状态如何。通常频率与硬件高分辨率源绑定,这就是为什么它提供了高精度(取决于硬件,但大多数现代硬件与高分辨率时钟源没有问题)。

第二种方法根据当前系统时钟值提供(日期)时间值。它的分辨率可能也很高,但它有一个主要的缺点: 这种时间值可能会受到不同系统时间调整的影响,即时区变化、夏时制(dST)变化、 NTP 服务器更新、系统休眠等。在某些情况下,你可以得到一个负的运行时间值,这可能会导致未定义行为。实际上这种时间源比第一种时间源更不可靠。

因此,时间间隔测量的第一原则是尽可能使用单调时钟。它通常具有较高的精度,设计可靠。

撤退策略

在实现便携式解决方案时,值得考虑一种后备策略: 如果可用,使用单调时钟; 如果系统中没有单调时钟,则使用时间戳方法。

窗户

在 MSDN 上有一篇很棒的文章叫做 获取高分辨率时间戳,是关于 Windows 上的时间测量的,它描述了你可能需要知道的关于软件和硬件支持的所有细节。要在 Windows 上获得高精度的时间戳,你应该:

  • 使用 查询性能频率查询计时器频率(每秒的滴答声) :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER freq;
    
    
    if (QueryPerformanceFrequency (&tcounter) != 0)
    freq = tcounter.QuadPart;
    

    计时器频率固定在系统引导上,因此您只需要获取一次。

  • 使用 QueryPerformanceCounter 查询性能计数器查询当前的刻度值:

    LARGE_INTEGER tcounter;
    LARGE_INTEGER tick_value;
    
    
    if (QueryPerformanceCounter (&tcounter) != 0)
    tick_value = tcounter.QuadPart;
    
  • scale the ticks to elapsed time, i.e. to microseconds:

    LARGE_INTEGER usecs = (tick_value - prev_tick_value) / (freq / 1000000);
    

According to Microsoft you should not have any problems with this approach on Windows XP and later versions in most cases. But you can also use two fallback solutions on Windows:

  • GetTickCount provides the number of milliseconds that have elapsed since the system was started. It wraps every 49.7 days, so be careful in measuring longer intervals.
  • GetTickCount64 is a 64-bit version of GetTickCount, but it is available starting from Windows Vista and above.

OS X (macOS)

OS X (macOS) has its own Mach absolute time units which represent a monotonic clock. The best way to start is the Apple's article Technical Q&A QA1398: Mach Absolute Time Units which describes (with the code examples) how to use Mach-specific API to get monotonic ticks. There is also a local question about it called clock_gettime alternative in Mac OS X which at the end may leave you a bit confused what to do with the possible value overflow because the counter frequency is used in the form of numerator and denominator. So, a short example how to get elapsed time:

  • get the clock frequency numerator and denominator:

    #include <mach/mach_time.h>
    #include <stdint.h>
    
    
    static uint64_t freq_num   = 0;
    static uint64_t freq_denom = 0;
    
    
    void init_clock_frequency ()
    {
    mach_timebase_info_data_t tb;
    
    
    if (mach_timebase_info (&tb) == KERN_SUCCESS && tb.denom != 0) {
    freq_num   = (uint64_t) tb.numer;
    freq_denom = (uint64_t) tb.denom;
    }
    }
    

    你只需要做一次。

  • 使用 mach_absolute_time查询当前的刻度值:

    uint64_t tick_value = mach_absolute_time ();
    
  • scale the ticks to elapsed time, i.e. to microseconds, using previously queried numerator and denominator:

    uint64_t value_diff = tick_value - prev_tick_value;
    
    
    /* To prevent overflow */
    value_diff /= 1000;
    
    
    value_diff *= freq_num;
    value_diff /= freq_denom;
    

    防止溢出的主要思想是在使用分子和分母之前将刻度缩小到所需的精度。由于初始计时器的分辨率是纳秒,我们将其除以 1000得到微秒。您可以在 Chromium 的 Time _ mac. c中找到相同的方法。如果你真的需要纳秒级的精度,可以考虑读取 如何在不溢出的情况下使用 mach _ Absol_ time?

Linux 和 UNIX

clock_gettime调用是任何 POSIX 友好系统上的最佳方式。它可以从不同的时钟源查询时间,我们需要的是 CLOCK_MONOTONIC。并非所有拥有 clock_gettime的系统都支持 CLOCK_MONOTONIC,因此首先你需要做的是检查它的可用性:

  • 如果将 _POSIX_MONOTONIC_CLOCK定义为值 >= 0,则意味着 CLOCK_MONOTONIC可用;
  • 如果 _POSIX_MONOTONIC_CLOCK定义为 0,这意味着你应该额外检查它是否在运行时工作,我建议使用 sysconf:

    #include <unistd.h>
    
    
    #ifdef _SC_MONOTONIC_CLOCK
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
    /* A monotonic clock presents */
    }
    #endif
    
  • otherwise a monotonic clock is not supported and you should use a fallback strategy (see below).

Usage of clock_gettime is pretty straight forward:

  • get the time value:

    #include <time.h>
    #include <sys/time.h>
    #include <stdint.h>
    
    
    uint64_t get_posix_clock_time ()
    {
    struct timespec ts;
    
    
    if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
    return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
    else
    return 0;
    }
    

    我把时间缩短到了微秒。

  • 用同样的方法计算与以前收到的时间值的差额:

    uint64_t prev_time_value, time_value;
    uint64_t time_diff;
    
    
    /* Initial time */
    prev_time_value = get_posix_clock_time ();
    
    
    /* Do some work here */
    
    
    /* Final time */
    time_value = get_posix_clock_time ();
    
    
    /* Time difference */
    time_diff = time_value - prev_time_value;
    

The best fallback strategy is to use the gettimeofday call: it is not a monotonic, but it provides quite a good resolution. The idea is the same as with clock_gettime, but to get a time value you should:

#include <time.h>
#include <sys/time.h>
#include <stdint.h>


uint64_t get_gtod_clock_time ()
{
struct timeval tv;


if (gettimeofday (&tv, NULL) == 0)
return (uint64_t) (tv.tv_sec * 1000000 + tv.tv_usec);
else
return 0;
}

同样,时间值缩放到微秒。

SGI IRIX

IRIX 具有 clock_gettime调用,但是它缺少 CLOCK_MONOTONIC。相反,它有自己的单调时钟源定义为 CLOCK_SGI_CYCLE,你应该使用它来代替 CLOCK_MONOTONICclock_gettime

Solaris 和 HP-UX

Solaris 有自己的高分辨率计时器接口 gethrtime,它以纳秒为单位返回当前计时器值。尽管较新版本的 Solaris 可能有 clock_gettime,但是如果需要支持旧版本的 Solaris,则可以坚持使用 gethrtime

用法很简单:

#include <sys/time.h>


void time_measure_example ()
{
hrtime_t prev_time_value, time_value;
hrtime_t time_diff;


/* Initial time */
prev_time_value = gethrtime ();


/* Do some work here */


/* Final time */
time_value = gethrtime ();


/* Time difference */
time_diff = time_value - prev_time_value;
}

HP-UX 缺少 clock_gettime,但是它支持 gethrtime,您应该像在 Solaris 上那样使用它。

贝奥斯

BeOS 还有自己的高分辨率定时器接口 system_time,它返回自计算机启动以来已经过去的微秒数。

示例用法:

#include <kernel/OS.h>


void time_measure_example ()
{
bigtime_t prev_time_value, time_value;
bigtime_t time_diff;


/* Initial time */
prev_time_value = system_time ();


/* Do some work here */


/* Final time */
time_value = system_time ();


/* Time difference */
time_diff = time_value - prev_time_value;
}

操作系统/2

OS/2 有自己的 API 来检索高精度的时间戳:

  • 使用 DosTmrQueryFreq(针对 GCC 编译器)查询计时器频率(每个单元的滴答声) :

    #define INCL_DOSPROFILE
    #define INCL_DOSERRORS
    #include <os2.h>
    #include <stdint.h>
    
    
    ULONG freq;
    
    
    DosTmrQueryFreq (&freq);
    
  • query the current ticks value with DosTmrQueryTime:

    QWORD    tcounter;
    unit64_t time_low;
    unit64_t time_high;
    unit64_t timestamp;
    
    
    if (DosTmrQueryTime (&tcounter) == NO_ERROR) {
    time_low  = (unit64_t) tcounter.ulLo;
    time_high = (unit64_t) tcounter.ulHi;
    
    
    timestamp = (time_high << 32) | time_low;
    }
    
  • scale the ticks to elapsed time, i.e. to microseconds:

    uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);
    

Example implementation

You can take a look at the plibsys library which implements all the described above strategies (see ptimeprofiler*.c for details).

接受的答案已经足够好了。但是我的解决方案更简单。我只是在 Linux 中测试,使用 gcc (Ubuntu 7.2.0-8ubuntu3.2)7.2.0。

也可以使用 gettimeofdaytv_sec是秒的一部分,而 tv_usec微秒,而不是 毫秒

long currentTimeMillis() {
struct timeval time;
gettimeofday(&time, NULL);


return time.tv_sec * 1000 + time.tv_usec / 1000;
}


int main() {
printf("%ld\n", currentTimeMillis());
// wait 1 second
sleep(1);
printf("%ld\n", currentTimeMillis());
return 0;
}

它打印:

1522139691342
1522139692342正好一秒。
^

对于 ANSI/ISO C11或更晚 ,您可以使用 timespec_get()获得毫秒、微秒或纳秒时间戳,如下所示:

#include <time.h>


/// Convert seconds to milliseconds
#define SEC_TO_MS(sec) ((sec)*1000)
/// Convert seconds to microseconds
#define SEC_TO_US(sec) ((sec)*1000000)
/// Convert seconds to nanoseconds
#define SEC_TO_NS(sec) ((sec)*1000000000)


/// Convert nanoseconds to seconds
#define NS_TO_SEC(ns)   ((ns)/1000000000)
/// Convert nanoseconds to milliseconds
#define NS_TO_MS(ns)    ((ns)/1000000)
/// Convert nanoseconds to microseconds
#define NS_TO_US(ns)    ((ns)/1000)


/// Get a time stamp in milliseconds.
uint64_t millis()
{
struct timespec ts;
timespec_get(&ts, TIME_UTC);
uint64_t ms = SEC_TO_MS((uint64_t)ts.tv_sec) + NS_TO_MS((uint64_t)ts.tv_nsec);
return ms;
}


/// Get a time stamp in microseconds.
uint64_t micros()
{
struct timespec ts;
timespec_get(&ts, TIME_UTC);
uint64_t us = SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec);
return us;
}


/// Get a time stamp in nanoseconds.
uint64_t nanos()
{
struct timespec ts;
timespec_get(&ts, TIME_UTC);
uint64_t ns = SEC_TO_NS((uint64_t)ts.tv_sec) + (uint64_t)ts.tv_nsec;
return ns;
}


// NB: for all 3 timestamp functions above: gcc defines the type of the internal
// `tv_sec` seconds value inside the `struct timespec`, which is used
// internally in these functions, as a signed `long int`. For architectures
// where `long int` is 64 bits, that means it will have undefined
// (signed) overflow in 2^64 sec = 5.8455 x 10^11 years. For architectures
// where this type is 32 bits, it will occur in 2^32 sec = 136 years. If the
// implementation-defined epoch for the timespec is 1970, then your program
// could have undefined behavior signed time rollover in as little as
// 136 years - (year 2021 - year 1970) = 136 - 51 = 85 years. If the epoch
// was 1900 then it could be as short as 136 - (2021 - 1900) = 136 - 121 =
// 15 years. Hopefully your program won't need to run that long. :). To see,
// by inspection, what your system's epoch is, simply print out a timestamp and
// calculate how far back a timestamp of 0 would have occurred. Ex: convert
// the timestamp to years and subtract that number of years from the present
// year.

对于我的一个 更彻底的答案,包括与 整个计时库我写的,见这里: 如何得到一个简单的 C 时间戳

@ Ciro Santilli 也是 这里提供了 C11的 timespec_get()功能的简明演示,这是我第一次学习如何使用这个函数。

我更详尽的回答中,我解释说在我的系统中,最好的分辨率是 ~ 20秒,但是分辨率依赖于硬件,并且可以因系统而异。