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

cor*_*rto 118 c portability time-precision

仅使用ANSI C,有没有办法以毫秒或更多精度测量时间?我正在浏览time.h但我只发现了第二个精确功能.

Rob*_*ble 88

没有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);
Run Code Online (Sandbox Code Playgroud)

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

  • 轻微的警告:函数gettimeofday()不是单调的,这意味着它可以跳跃(甚至倒退),如果,例如,你的机器正试图跟上网络时间服务器或其他时间源同步. (27认同)
  • 值得注意的是,`timeval :: tv_usec`总是在一秒之内,它正在循环.即为了使时间差大于1秒,你应该:`long usec_diff =(e.tv_sec - s.tv_sec)*1000000 +(e.tv_usec - s.tv_usec); (6认同)
  • @Dipstick:但请注意,例如NTP从不向后移动你的时钟,直到你明确告诉它这样做. (4认同)
  • 确切地说:在ISO C99(我认为这部分与ANSI C兼容)中,甚至没有*任何*时间分辨率的保证.(ISO C99,7.23.1p4) (3认同)
  • @AlexanderMalakhov 时间减法逻辑封装在 `timersub` 函数内。我们可以按原样使用“tval_result”值(tv_sec 和 tv_usec)。 (2认同)

Nic*_*unt 45

#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
Run Code Online (Sandbox Code Playgroud)

  • 由于它的每秒时钟与它的值无关,因此clock()/ CLOCKS_PER_SEC的结果值将以秒为单位(至少它应该是).除以1000转,以毫秒为单位. (15认同)
  • 根据C参考手册,clock_t值可以在大约36分钟左右开始.如果要测量长计算,则需要注意这一点. (13认同)
  • 还要注意整数除法`CLOCKS_PER_SEC/1000`可能是不精确的,这可能会影响最终结果(尽管根据我的经验,`CLOCKS_PER_SEC`一直是1000的倍数).执行`(1000*clock())/ CLOCKS_PER_SEC`不易受分割不精确的影响,但另一方面更容易出现溢出.只是要考虑一些问题. (4认同)
  • 这不是测量CPU时间而不是测量时间吗? (4认同)
  • `clock()` [测量](/sf/ask/1902515261/) Windows 上的挂机时间和大多数其他流行操作系统上的 CPU 时间。 (2认同)

小智 26

我总是使用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);
}
Run Code Online (Sandbox Code Playgroud)

  • clock_gettime()不是ANSI C. (5认同)
  • @ PowerApp101没有好的/强大的ANSI C方式.许多其他答案都依赖于POSIX而不是ANCI C.话虽如此,我相信今天.@Dipstick今天,我相信大多数现代平台[引用需要]支持`clock_gettime(CLOCK_MONOTONIC,...)`,甚至还有功能测试宏`_POSIX_MONOTONIC_CLOCK`. (2认同)

Ale*_*kin 19

实施便携式解决方案

正如在这里已经提到的那样,没有适当的ANSI解决方案,对于时间测量问题具有足够的精度,我想写下如何获得便携式的方法,如果可能的话,还有高分辨率的时间测量解决方案.

单调时钟与时间戳

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

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

第一个使用单调时钟计数器(有时称为刻度计数器),它以预定义的频率计算刻度,因此如果您有刻度值并且频率已知,则可以轻松地将刻度转换为经过时间.实际上并不能保证单调时钟以任何方式反映当前系统时间,它也可能计算自系统启动以来的时间.但它保证了无论系统状态如何,时钟总是以不断增长的方式运行.通常频率被绑定到硬件高分辨率源,这就是它提供高精度的原因(取决于硬件,但大多数现代硬件都没有高分辨率时钟源的问题).

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

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

后备策略

在实施便携式解决方案时,值得考虑一种回退策略:如果可用则使用单调时钟,如果系统中没有单调时钟,则使用回退到时间戳.

视窗

有一篇很棒的文章称为在MSDN上获取有关Windows上时间测量的高分辨率时间戳,它描述了您可能需要了解的有关软件和硬件支持的所有详细信息.要在Windows上获得高精度时间戳,您应该:

据微软称,在大多数情况下,你不应该在Windows XP和更高版本上使用这种方法有任何问题.但您也可以在Windows上使用两种后备解决方案:

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

OS X(macOS)

OS X(macOS)有自己的Mach绝对时间单位,表示单调时钟.最好的方法是Apple的文章技术问答QA1398:Mach绝对时间单位,它描述了(使用代码示例)如何使用特定于Mach的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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    你只需要这样做一次.

  • 查询当前的刻度值mach_absolute_time:

    uint64_t tick_value = mach_absolute_time ();
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用先前查询的分子和分母将刻度缩放到经过的时间,即微秒:

    uint64_t value_diff = tick_value - prev_tick_value;
    
    /* To prevent overflow */
    value_diff /= 1000;
    
    value_diff *= freq_num;
    value_diff /= freq_denom;
    
    Run Code Online (Sandbox Code Playgroud)

    防止溢出的主要思想是在使用分子和分母之前将滴答缩小到所需的精度.由于初始定时器分辨率是以纳秒为单位,我们将其除以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定义为0它意味着你应该另外检查它是否在运行时工作,我建议使用sysconf:

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

使用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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    我把时间缩短到了微秒.

  • 计算与前一个时间值的差异以同样的方式收到:

    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;
    
    Run Code Online (Sandbox Code Playgroud)

最好的后备策略是使用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;
}
Run Code Online (Sandbox Code Playgroud)

同样,时间值缩小到微秒.

SGI IRIX

IRIXclock_gettime电话,但缺乏CLOCK_MONOTONIC.相反,它有它自己单调的时钟源定义为CLOCK_SGI_CYCLE你应该使用它,而不是CLOCK_MONOTONICclock_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;
}
Run Code Online (Sandbox Code Playgroud)

HP-UX缺乏clock_gettime,但它支持gethrtime您应该以与Solaris相同的方式使用它.

BeOS的

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;
}
Run Code Online (Sandbox Code Playgroud)

OS/2

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

  • 查询定时器频率(每单位刻度)DosTmrQueryFreq(对于GCC编译器):

    #define INCL_DOSPROFILE
    #define INCL_DOSERRORS
    #include <os2.h>
    #include <stdint.h>
    
    ULONG freq;
    
    DosTmrQueryFreq (&freq);
    
    Run Code Online (Sandbox Code Playgroud)
  • 查询当前的ticks值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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 将刻度缩放到经过的时间,即微秒:

    uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);
    
    Run Code Online (Sandbox Code Playgroud)

示例实现

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


Cir*_*四事件 7

timespec_get 来自C11

返回最多纳秒,舍入到实现的分辨率.

看起来像POSIX'的ANSI ripoff 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;
}
Run Code Online (Sandbox Code Playgroud)

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_clock:C++跨平台高分辨率计时器

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;
}
Run Code Online (Sandbox Code Playgroud)

如此清楚:


Lee*_*Lee 5

接受的答案已经足够好了。但我的解决方案更简单。我只是在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;
 }
Run Code Online (Sandbox Code Playgroud)

它打印:

1522139691342
1522139692342,正好一秒钟。
^