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

时间:2008-12-11 23:09:50

标签: c portability time-precision

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

8 个答案:

答案 0 :(得分:85)

没有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

答案 1 :(得分:44)

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

答案 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);
}

答案 3 :(得分:16)

实施便携式解决方案

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

单调时钟与时间戳

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

  • monotonic clock;
  • 当前(日期)时间戳。

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

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

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

后备策略

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

<强>窗

在MSDN上有一篇名为Acquiring high-resolution time stamps的文章,关于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天包裹一次,所以要小心测量更长的间隔。
  • GetTickCount64GetTickCount的64位版本,但从Windows Vista及更高版本开始提供。

OS X(macOS)

OS X(macOS)有自己的Mach绝对时间单位,表示单调时钟。最好的方法是Apple的文章Technical Q&A QA1398: Mach Absolute Time Units,它描述了(使用代码示例)如何使用特定于Mach的API来获得单调的滴答声。还有一个关于它的本地问题叫做clock_gettime alternative in Mac OS X,最后可能会让你对可能的值溢出有点混淆,因为计数器频率以分子和分母的形式使用。所以,一个简短的例子说明如何获得经过的时间:

  • 获取时钟频率分子和分母:

    #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中使用的相同方法。如果您确实需要纳秒精度,请考虑阅读How can I use mach_absolute_time without overflowing?

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
    
  • 否则不支持单调时钟,您应该使用后备策略(见下文)。

使用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_MONOTONIC代替clock_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的

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)。

答案 4 :(得分:6)

来自C11的

timespec_get

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

看起来像是来自POSIX'clock_gettime的ANSI ripoff。

示例:在Ubuntu 15.10上每100毫秒执行printf

#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 standard draft 7.27.2.5“timespec_get函数说”:

  

如果base是TIME_UTC,则tv_sec成员被设置为自a以来的秒数   实现定义了epoch,截断为整个值,tv_nsec成员是   设置为纳秒的整数,四舍五入到系统时钟的分辨率。 (321)

     

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

C ++ 11也获得std::chrono::high_resolution_clockC++ Cross-Platform High-Resolution Timer

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

如此清楚:

答案 5 :(得分:4)

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

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

答案 6 :(得分:1)

接受的答案已经足够了。但我的解决方案更简单。我只是在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,恰好是一秒钟。

答案 7 :(得分:-3)

在Windows下:

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