在C语言中如何以微秒为单位获取时间戳?

52

如何在C语言中获取微秒级别的时间戳?

我想要做:

struct timeval tv;
gettimeofday(&tv,NULL);
return tv.tv_usec;

但是这样返回的一些无意义的值,如果我获得了两个时间戳,第二个时间戳可以比第一个时间戳小或大(第二个时间戳应该始终更大)。是否可能将gettimeofday返回的魔术整数转换为实际可用的正常数字?


11
tv_usec并不是指“当前时间的微秒数”,而是指“当前时间的微秒数对1000000取模后的值”。 - ruslik
1
@ruslik 我该如何将它转换为“普通”的数字? - Kristina Brooks
1
@Nick Brooks:嗯...“普通”数字有一个坏习惯,就是它们拥有范围,因此总会存在这样的值,即没有更大的值。我认为你应该重新审查一下你的算法。尝试使用(sec2-sec1)*1000000+(usec2-usec1)。或者更好的方法是,为了避免秒数溢出的可能性,如果你知道时间周期小于一秒,那么当秒数较小时,只需将其加上1000000。 - ruslik
1
我只需要微秒级时间戳来计算滚动惯性。我需要类似于time()但是精确到微秒的东西。 - Kristina Brooks
1
如何使用 ANSI C 以毫秒为单位测量时间 - Nick Van Brunt
显示剩余2条评论
9个回答

59

您还需要添加秒钟:

unsigned long time_in_micros = 1000000 * tv.tv_sec + tv.tv_usec;

请注意,这仅持续大约232/106 =~ 4295秒,或大约71分钟(在典型的32位系统上)。


17
使用64位整型来存储微秒数,确保精度。 - Havoc P
3
解决方案的问题:由于您没有从0开始使用tv_sec,time_in_micros可能会在任何时间溢出!无论是作为一个单独的uint64_t还是作为两个32位值,都必须使用64位时间。至少,在开始计时时,请保存始发时间的tv_sec值,并将其从所有未来的计时值中减去。 - Tom West
1
如果你知道自己在做什么,让32位系统中的值翻转并不是那么糟糕的事情。如果在你的实现中间隔永远不会超过71分钟,那么你仍然可以从current_microseconds-start_time的计算中获得合理的值。 - Zouppen

25

要获取微秒级时间戳,您有两个选择。第一个(也是最好的)选择是直接使用timeval类型:

struct timeval GetTimeStamp() {
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv;
}

第二种方法,对我来说不太理想,是使用 timeval 构建一个 uint64_t

uint64_t GetTimeStamp() {
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv.tv_sec*(uint64_t)1000000+tv.tv_usec;
}

2
为什么要使用(uint64_t)1000000而不是1000000ull - Mooing Duck
7
对我来说(比你经验较少的程序员),(uint64_t)1000000 实际上比 1000000ull 更易读。现在我看到它们放在一起,后者也有意义,但是即使作为一个年轻的程序员,我也能识别出第一个,而后者则需要我查找以验证其含义。 - Gabriel Staples
5
ull并不在所有系统上指定相同数量的位。 uintXX_t可以确保你得到确切想要的内容。 - kylefinn

14
在C语言中如何获取微秒级别的时间戳?
以下是与问题标题相关的通用答案:

如何在C语言中获取简单的时间戳

  1. 使用函数millis()获取毫秒(ms)级别的时间戳,
  2. 使用micros()获取微秒(us)级别的时间戳,以及
  3. 使用nanos()获取纳秒(ns)级别的时间戳。

快速总结:如果你急需获取时间戳且使用Linux或POSIX系统,请直接跳转到下面标题为“millis(), micros(), and nanos()”的部分,并使用这些函数。如果你使用的是C11而不是Linux或POSIX系统,则需要将那些函数中的clock_gettime()替换为timespec_get()

C语言中的两个主要时间戳函数:

  1. C11: timespec_get()是C11或更高版本的标准的一部分,但它不允许选择要使用的时钟类型。它也适用于C++17。在此处查看std::timespec_get()的文档。然而,对于C++11及更高版本,我更喜欢使用不同的方法,可以指定分辨率和时钟类型,正如我在这里展示的答案中所示:在C++中获取准确的执行时间(微秒)

    C11的timespec_get()解决方案比C ++解决方案更加有限,因为您无法指定时钟分辨率或单调性(“单调”时钟被定义为仅向前计数并且永远不会向后或跳跃 - 例如:用于时间校正)。在测量时间差异时,希望使用单调时钟以确保您从未将时钟校正跳跃视为“测量”的时间的一部分。

    由于我们无法指定要使用的时钟,因此timespec_get()返回的时间戳值的分辨率可能取决于您的硬件架构操作系统编译器。可以通过快速连续进行1000次或更多次测量,然后找到任何两个连续测量之间的最小差异来近似计算此函数的分辨率。时钟的实际分辨率保证等于或小于该最小差异。

    我在我的timinglib.c定时库的get_estimated_resolution()函数中演示了这一点,该库适用于Linux。

  2. Linux和POSIX:在C中,timespec_get()Linux和POSIX函数clock_gettime()更好,它也可以在Linux或POSIX系统上的C++中正常工作。clock_gettime()允许您选择所需的时钟。您可以使用clock_getres()读取指定的时钟分辨率,尽管它也不会给出您的硬件的真实时钟分辨率。相反,它为您提供struct timespectv_nsec成员的单位。使用我刚才描述并在我的timinglib.c/.h文件中的get_estimated_resolution()函数来估算分辨率。

因此,如果您在Linux或POSIX系统上使用C语言,我强烈建议使用clock_gettime()而不是timespec_get()

C11的timespec_get()(可行)和Linux/POSIX的clock_gettime()(更好):

以下是如何使用这两个函数:

  1. C11's timespec_get()
    1. https://en.cppreference.com/w/c/chrono/timespec_get
    2. Works in C, but doesn't allow you to choose the clock to use.
    3. Full example, with error checking:
      #include <stdint.h> // `UINT64_MAX`
      #include <stdio.h>  // `printf()`
      #include <time.h>   // `timespec_get()`
      
      /// Convert seconds to nanoseconds
      #define SEC_TO_NS(sec) ((sec)*1000000000)
      
      uint64_t nanoseconds;
      struct timespec ts;
      int return_code = timespec_get(&ts, TIME_UTC);
      if (return_code == 0)
      {
          printf("Failed to obtain timestamp.\n");
          nanoseconds = UINT64_MAX; // use this to indicate error
      }
      else
      {
          // `ts` now contains your timestamp in seconds and nanoseconds! To 
          // convert the whole struct to nanoseconds, do this:
          nanoseconds = SEC_TO_NS((uint64_t)ts.tv_sec) + (uint64_t)ts.tv_nsec;
      }
      
  2. Linux/POSIX's clock_gettime() -- USE THIS ONE WHENEVER POSSIBLE!
    1. https://man7.org/linux/man-pages/man3/clock_gettime.3.html (best reference for this function) and:
    2. https://linux.die.net/man/3/clock_gettime
    3. Works in C on Linux or POSIX systems, and allows you to choose the clock to use!
      1. I choose the CLOCK_MONOTONIC_RAW clock, which is best for obtaining timestamps used to time things on your system.
      2. See definitions for all of the clock types here, too, such as CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW, etc: https://man7.org/linux/man-pages/man3/clock_gettime.3.html
      3. Another popular clock to use is CLOCK_REALTIME. Do NOT be confused, however! "Realtime" does NOT mean that it is a good clock to use for "realtime" operating systems, or precise timing. Rather, it means it is a clock which will be adjusted to the "real time", or actual "world time", periodically, if the clock drifts. Again, do NOT use this clock for precise timing usages, as it can be adjusted forwards or backwards at any time by the system, outside of your control.
    4. Full example, with error checking:
      // This line **must** come **before** including <time.h> in order to
      // bring in the POSIX functions such as `clock_gettime() from <time.h>`!
      #define _POSIX_C_SOURCE 199309L
      
      #include <errno.h>  // `errno`
      #include <stdint.h> // `UINT64_MAX`
      #include <stdio.h>  // `printf()`
      #include <string.h> // `strerror(errno)`
      #include <time.h>   // `clock_gettime()` and `timespec_get()`
      
      /// Convert seconds to nanoseconds
      #define SEC_TO_NS(sec) ((sec)*1000000000)
      
      uint64_t nanoseconds;
      struct timespec ts;
      int return_code = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
      if (return_code == -1)
      {
          printf("Failed to obtain timestamp. errno = %i: %s\n", errno, 
              strerror(errno));
          nanoseconds = UINT64_MAX; // use this to indicate error
      }
      else
      {
          // `ts` now contains your timestamp in seconds and nanoseconds! To 
          // convert the whole struct to nanoseconds, do this:
          nanoseconds = SEC_TO_NS((uint64_t)ts.tv_sec) + (uint64_t)ts.tv_nsec;
      }
      

millis(), micros(), 和 nanos():

这里是我在C语言中使用的millis(), micros(), 和 nanos()函数,用于简单的时间戳和代码速度分析。

下面我使用了Linux/POSIX的clock_gettime()函数。如果您正在使用C11或更高版本,在系统上没有clock_gettime()可用的情况下,只需将下面所有使用clock_gettime(CLOCK_MONOTONIC_RAW, &ts)的地方替换为timespec_get(&ts, TIME_UTC)即可。

从我的eRCaGuy_hello_world存储库中获取最新版本的代码:

  1. timinglib.h
  2. timinglib.c
// This line **must** come **before** including <time.h> in order to
// bring in the POSIX functions such as `clock_gettime() from <time.h>`!
#define _POSIX_C_SOURCE 199309L
        
#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;
    clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
    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;
    clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
    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;
    clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
    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.

时间戳分辨率:

在我的x86-64 Linux Ubuntu 18.04系统上,使用gcc编译器,clock_getres()返回1纳秒的分辨率。

对于clock_gettime()timespec_get(),我还进行了经验测试,快速地获取1000个时间戳,尽可能快地获取(请参见我的timinglib.c计时库中的get_estimated_resolution()函数),并查看时间戳样本之间的最小间隔。当使用timespec_get(&ts, TIME_UTC)clock_gettime(CLOCK_MONOTONIC, &ts)时,在我的系统上可以获得约14~26纳秒的范围,而使用clock_gettime(CLOCK_MONOTONIC_RAW, &ts)时,可以获得约75~130纳秒的范围。这可以被认为是这些函数的粗略“实际分辨率”。请参见timinglib_get_resolution.c中的测试代码,并查看我get_estimated_resolution()get_specified_resolution()函数的定义(这些函数由该测试代码使用)在timinglib.c中。

这些结果是硬件特定的,您在自己的硬件上的结果可能会有所不同。

参考资料:

  1. 我引用的cppreference.com文档来源。
  2. 这里由@Ciro Santilli新疆棉花提供的答案
  3. [我的回答] 我关于usleep()nanosleep()的回答——它提醒我需要做#define _POSIX_C_SOURCE 199309L才能从<time.h>中引入clock_gettime() POSIX函数!
  4. https://linux.die.net/man/3/clock_gettime
  5. https://man7.org/linux/man-pages/man3/clock_gettime.3.html
    1. 还提到了以下要求:

    _POSIX_C_SOURCE >= 199309L

    1. 此外,此处还介绍了所有时钟类型的定义,例如CLOCK_REALTIMECLOCK_MONOTONICCLOCK_MONOTONIC_RAW等。

另请参阅:

  1. 我的回答只适用于ANSI/ISO C11或更高版本: 如何使用ANSI C测量毫秒时间?
  2. 我的三组时间戳函数(相互交叉链接):
    1. 对于C时间戳,请参见我的回答: 如何在C中获取微秒级时间戳?
    2. 对于C++高分辨率时间戳,请参见我的回答: 这是如何在C ++中获取简单的类似于毫秒、微秒和纳秒的时间戳的方法
    3. 对于Python高分辨率时间戳,请参见我的回答: 如何在Python中获得毫秒和微秒分辨率的时间戳?
  3. https://en.cppreference.com/w/c/chrono/clock
    1. POSIX clock_gettime(): https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
  4. Linux上的clock_gettime(): https://linux.die.net/man/3/clock_gettime
    1. 注意:对于C11及更高版本,您可以像我上面做的那样使用timespec_get(),而不是POSIX clock_gettime()https://en.cppreference.com/w/c/chrono/clock说:

      在C11中使用timespec_get

    2. 但是,使用clock_gettime()可以让您选择所需的时钟ID,以获取所需的时钟类型!另请参见这里: ***** https://people.cs.rutgers.edu/~pxk/416/notes/c-tutorials/gettime.html
✓ 自2022年4月3日起已完成:由于timespec_getres()直到C23才支持,因此更新我的示例以包括在Linux上使用POSIX clock_gettime()clock_getres()函数的示例。我想要知道在给定系统上可以期望多好的时钟分辨率。是毫秒级、微秒级、纳秒级还是其他什么? 供参考,请参阅:
  1. https://linux.die.net/man/3/clock_gettime
  2. https://people.cs.rutgers.edu/~pxk/416/notes/c-tutorials/gettime.html
  3. https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
  4. 答案: clock_getres() 返回1 ns,但实际分辨率约为14~27 ns,根据我的get_estimated_resolution()函数在这里:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/c/timinglib.c。请参见以下结果:
    1. https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/c/timinglib_get_resolution.c#L46-L77
    2. 为了获得最佳和最一致的时间定时,请激活Linux SCHED_RR 软实时轮询调度程序。请参见我的答案,了解有关clock_nanosleep()的更改:How to configure the Linux SCHED_RR soft real-time round-robin scheduler so that clock_nanosleep() can have improved resolution of ~4 us down from ~ 55 us

8

struct timeval包含两个组件,即秒和微秒。具有微秒精度的时间戳表示自纪元以来的秒数存储在tv_sec字段中,分数微秒存储在tv_usec中。因此,您不能忽略tv_sec并期望得到合理的结果。

如果您使用Linux或*BSD,则可以使用timersub()减去两个struct timeval值,这可能是您想要的。


7

C11 中的 timespec_get

返回精度高达纳秒,按实现的分辨率四舍五入。

#include <time.h>
struct timespec ts;
timespec_get(&ts, TIME_UTC);
struct timespec {
    time_t   tv_sec;        /* seconds */
    long     tv_nsec;       /* nanoseconds */
};

请查看我在此处的其他答案中的更多详细信息:如何使用ANSI C测量毫秒时间?

1
谢谢。我将其扩展为3个单独的函数:millis()micros()nanos()在我的答案中,分别用于毫秒、微秒和纳秒级别的时间戳。 - Gabriel Staples

2
但是这样返回了一些无意义的值,如果我有两个时间戳,第二个时间戳可能比第一个时间戳小或大(第二个时间戳应该始终更大)。
你认为是什么原因呢?这个值很可能没问题。这就像秒和分钟的情况一样——当你用分钟和秒测量时间时,当秒数到达60时,它会滚动到零。
为了将返回的值转换为“线性”数字,你可以乘以秒数并加上微秒数。但是如果我计算正确,一年大约是1e6*60*60*24*360 μsec,这意味着你需要超过32位来存储结果:
$ perl -E '$_=1e6*60*60*24*360; say int log($_)/log(2)'
44

这可能是将原始返回值拆分为两个部分的原因之一。


0

使用无符号长整型(即64位单位)来表示系统时间:

typedef unsigned long long u64;

u64 u64useconds;
struct timeval tv;

gettimeofday(&tv,NULL);
u64useconds = (1000000*tv.tv_sec) + tv.tv_usec;

0
迟做总比不做好!这个小程序可以用作获取微秒级时间戳并计算进程时间的最快方法:
#include <sys/time.h>
#include <stdio.h>
#include <time.h>

struct timeval GetTimeStamp() 
{
    struct timeval tv;
    gettimeofday(&tv,NULL);
    return tv;
}

int main()
{
    struct timeval tv= GetTimeStamp(); // Calculate time
    signed long time_in_micros = 1000000 * tv.tv_sec + tv.tv_usec; // Store time in microseconds
    
    getchar(); // Replace this line with the process that you need to time

    printf("Elapsed time: %ld microsecons\n", (1000000 * GetTimeStamp().tv_sec + GetTimeStamp().tv_usec) - time_in_micros);
    
}

你可以用一个函数/进程来替换getchar()。最后,你可以将差异存储在有符号长整型中,而不是打印出来。该程序在Windows 10中运行良好。


-1
首先,我们需要了解微秒范围,即000_000到999_999(1000000微秒等于1秒)。tv.tv_usec将返回从0到999999的值,而不是000000到999999,因此在与秒一起使用时,我们可能会得到2.1秒而不是2.000001秒,因为仅谈论tv_usec 000001本质上是1。最好插入


if(tv.tv_usec<10)
{
 printf("00000");
} 
else if(tv.tv_usec<100&&tv.tv_usec>9)// i.e. 2digits
{
 printf("0000");
}

等等……


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