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


127

仅使用ANSI C,有没有办法以毫秒或更高的精度来测量时间?我正在浏览time.h,但只找到了第二个精度函数。


4
注意精度和准确性之间的差异。你可以用毫秒时间精度采取的时间,以秒和乘以1000,但是这是没有用的。ms精度函数不一定具有ms精度-尽管它们通常比1s精度好。
史蒂夫·杰索普

2
简单的答案是“否”,ANSI C不支持毫秒精度或更高的精度。更复杂的答案取决于您要执行的操作-坦率地说,整个区域都是一场噩梦-即使您允许使用广泛使用的Posix函数。您使用术语“度量”,因此我假设您对间隔感兴趣,而不是“挂钟”时间。但是,您是否尝试通过过程来测量绝对时间段或CPU使用率?
机油尺

2
只是想对SOF说再一次保存了我的培根;-)
corlettk 2011年

Answers:


92

没有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在我的机器上返回。


30
轻微警告:gettimeofday()不是单调的,这意味着,例如,如果您的计算机尝试与网络时间服务器或其他时间源保持同步,它可能会跳来跳去(甚至向后走)。
机油尺

3
准确地说:在ISO C99(我认为这部分与ANSI C兼容)中,甚至不能保证任何时间分辨率。(ISO C99,7.23.1p4)
罗兰·

6
值得注意的是,timeval::tv_usec总是在不到一秒钟的时间里,这是循环的。即,为了采用大于1秒的时间差,您应该:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
Alexander Malakhov 2012年

4
@Dipstick:但是请注意,例如NTP永远不会向后移动时钟,除非您明确告诉它这样做。
thejh

1
@AlexanderMalakhov时间减法逻辑封装在timersub函数内部。我们可以tval_result按原样使用值(tv_sec和tv_usec)。
x4444 '19

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

在许多系统上,CLOCKS_PER_SEC设置为1000000。在以这种方式使用它之前,请先打印其值以确保确定。
ysap 2012年

16
由于是每秒时钟,所以它的值无关紧要,clock()/ CLOCKS_PER_SEC的结果值将以秒为单位(至少应该是秒)。除以1000会将其转换为毫秒。
David Young

14
根据《 C参考手册》,clock_t值可以在大约36分钟左右开始回绕。如果您要进行较长的计算,则需要注意这一点。
Cyber​​Skull

4
还要注意,整数除法CLOCKS_PER_SEC / 1000可能不精确,这可能会影响最终结果(尽管根据我的经验,CLOCKS_PER_SEC它始终是1000的倍数)。这样做(1000 * clock()) / CLOCKS_PER_SEC不易受到除法不精确性的影响,但另一方面更容易发生溢出。只是一些要考虑的问题。
Cornstalks

4
这不是衡量CPU时间而不是墙时间吗?
krs013

27

我总是使用clock_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);
}

5
clock_gettime()不是ANSI C.
PowerApp101

1
同样,在许多系统(包括许多Linux平台)上也未实现CLOCK_MONOTONIC。
机油尺

2
@ PowerApp101没有良好/可靠的ANSI C方法。许多其他答案取决于POSIX而不是ANCIC。话虽如此,我相信今天。@Dipstick今天,我相信大多数现代平台都需要支持clock_gettime(CLOCK_MONOTONIC, ...),甚至还有功能测试宏_POSIX_MONOTONIC_CLOCK
omn​​inonsense

22

实施便携式解决方案

正如这里已经提到的那样,没有合适的ANSI解决方案来解决时间测量问题,因此我想写一些有关如何获得便携式和高分辨率时间测量解决方案的方法。

单调时钟与时间戳

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

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

第一个使用单调时钟计数器(有时称为滴答计数器),它以预定义的频率对滴答进行计数,因此,如果您具有滴答值并且知道频率,则可以轻松地将滴答转换为经过的时间。实际上,不能保证单调时钟以任何方式反映当前系统时间,它也可能在系统启动后计数滴答声。但是,它保证了无论系统状态如何,时钟总是以递增的方式运行。通常,频率与硬件高分辨率源有关,这就是为什么它提供高精度的原因(取决于硬件,但是大多数现代硬件在高分辨率时钟源上都没有问题)。

第二种方法基于当前系统时钟值提供(日期)时间值。它可能也具有高分辨率,但是它有一个主要缺点:这种时间值可能会受到不同系统时间调整的影响,例如,时区更改,夏令时(DST)更改,NTP服务器更新,系统休眠等上。在某些情况下,您会获得一个负的经过时间值,这可能导致未定义的行为。实际上,这种时间源不如第一种时间源可靠。

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

后备策略

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

视窗

在MSDN上,有一篇很棒的文章叫做“ 获取高分辨率时间戳记”,其中涉及Windows上的时间测量,其中描述了您可能需要了解的有关软件和硬件支持的所有详细信息。要在Windows上获取高精度时间戳,您应该:

  • 使用QueryPerformanceFrequency查询计时器频率(每秒点数)

    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;
  • 将刻度调整为经过的时间,即微秒:

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

根据Microsoft的说法,在大多数情况下,在Windows XP和更高版本上使用此方法应该不会有任何问题。但是您也可以在Windows上使用两种后备解决方案:

  • GetTickCount提供自系统启动以来经过的毫秒数。它每49.7天包装一次,因此在测量较长的间隔时要小心。
  • GetTickCount64是的64位版本GetTickCount,但从Windows Vista及更高版本开始提供。

OS X(macOS)

OS X(macOS)有自己的马赫绝对时间单位,代表单调时钟。最好的开始方法是Apple的文章《技术问答QA1398:马赫绝对时间单位》,该文章(与代码示例一起)描述了如何使用特定于马赫的API来获取单调刻度。在Mac OS X中还有一个关于它的本地问题,称为clock_gettime替代,最后,您可能会有点困惑可能的值溢出,因为计数器频率以分子和分母的形式使用。因此,一个简短的示例如何获得经过时间:

  • 得到时钟频率分子和分母:

    #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 ();
  • 使用先前查询的分子和分母将刻度线缩放为经过的时间,即微秒:

    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_absolute_time而不溢出?

Linux和UNIX

clock_gettime在任何适合POSIX的系统上,致电都是您最好的方式。它可以查询来自不同时钟源的时间,而我们需要的是CLOCK_MONOTONIC。并非所有具有clock_gettime支持功能的系统CLOCK_MONOTONIC,因此您需要做的第一件事就是检查其可用性:

  • 如果_POSIX_MONOTONIC_CLOCK定义为一个值,>= 0则表示该CLOCK_MONOTONIC值可用;
  • 如果_POSIX_MONOTONIC_CLOCK定义了if ,0则意味着您应该另外检查它是否在运行时有效,我建议使用sysconf

    #include <unistd.h>
    
    #ifdef _SC_MONOTONIC_CLOCK
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
        /* A monotonic clock presents */
    }
    #endif
  • 否则,不支持单调时钟,您应该使用后备策略(请参见下文)。

的用法clock_gettime很简单:

  • 得到时间值:

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

最好的后备策略是使用该gettimeofday调用:它不是单调的,但它提供了很好的分辨率。这个想法与相同clock_gettime,但是要获得时间值,您应该:

#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

IRIXclock_gettime电话,但没有CLOCK_MONOTONIC。取而代之的是,它定义了自己的单调时钟源CLOCK_SGI_CYCLE,您应该使用它而不是CLOCK_MONOTONICwith clock_gettime

Solaris和HP-UX

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

用法很简单:

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

OS / 2

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

  • 使用DosTmrQueryFreq(对于GCC编译器)查询计时器频率(每单位拍数):

    #define INCL_DOSPROFILE
    #define INCL_DOSERRORS
    #include <os2.h>
    #include <stdint.h>
    
    ULONG freq;
    
    DosTmrQueryFreq (&freq);
  • 使用以下命令查询当前的刻度值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;
    }
  • 将刻度调整为经过的时间,即微秒:

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

示例实施

您可以看一下实现上述所有策略的plibsys库(有关详细信息,请参见ptimeprofiler * .c)。


“没有适当的ANSI解决方案来解决时间测量问题,它的timespec_get
精度很高

1
这仍然是衡量代码执行时间的错误方法。timespec_get不是单调的。
亚历山大·萨普里金

11

timespec_get 从C11

返回最多十亿分之一秒,四舍五入到实现的分辨率。

看起来像是从POSIX'提取的ANSI clock_gettime

示例:printf在Ubuntu 15.10上每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)

321)尽管struct timespec对象以纳秒分辨率描述时间,但是可用分辨率取决于系统,甚至可能大于1秒。

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

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

很清楚:


截至2017年,这是正确的答案,即使MSVC也具有此功能; 在基准测试方面寻找一些能读取芯片寄存器的信息(具有PT扩展功能的x86处理器的更新版本,以及相应的Linux内核/ perf的更新版本)

4

您可能获得的最佳精度是使用仅x86的“ rdtsc”指令,该指令可以提供时钟级别的分辨率(当然,必须考虑到rdtsc调用本身的成本,可以很容易地衡量该成本)。应用程序启动)。

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


3
您可能还需要担心处理器的亲和力,因为在某些计算机上,您可能会将RDTSC调用发送到多个处理器,并且它们的RDTSC计数器可能不同步。
威尔·迪恩

1
而且,某些处理器没有单调递增的TSC-想想省电模式会降低CPU频率。除了非常短的本地化时间之外,将RDTSC用于任何其他事情都是非常糟糕的主意。
2013年

顺便说一句,@ WillDean提到的核心漂移问题以及使用rdtsc进行计时是许多游戏无法(早期?)多核AMD64 CPU不能正常工作的原因-我不得不限制x2 4400+的单核亲和力一些头衔。
2013年

2

公认的答案足够好,但是我的解决方案更简单。我只是在Linux上测试,使用gcc(Ubuntu 7.2.0-8ubuntu3.2)7.2.0。

在其他情况下gettimeofday,则tv_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,恰好是一秒钟。


-4

在Windows下:

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

1
这是按要求的ansi C吗?
Gyom,2015年
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.