如何在C语言中获取微秒级别的时间戳?
我想要做:
struct timeval tv;
gettimeofday(&tv,NULL);
return tv.tv_usec;
但是这样返回的一些无意义的值,如果我获得了两个时间戳,第二个时间戳可以比第一个时间戳小或大(第二个时间戳应该始终更大)。是否可能将gettimeofday
返回的魔术整数转换为实际可用的正常数字?
您还需要添加秒钟:
unsigned long time_in_micros = 1000000 * tv.tv_sec + tv.tv_usec;
请注意,这仅持续大约232/106 =~ 4295秒,或大约71分钟(在典型的32位系统上)。
要获取微秒级时间戳,您有两个选择。第一个(也是最好的)选择是直接使用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;
}
(uint64_t)1000000
而不是1000000ull
? - Mooing Duck(uint64_t)1000000
实际上比 1000000ull
更易读。现在我看到它们放在一起,后者也有意义,但是即使作为一个年轻的程序员,我也能识别出第一个,而后者则需要我查找以验证其含义。 - Gabriel Staplesmillis()
获取毫秒(ms)级别的时间戳,micros()
获取微秒(us)级别的时间戳,以及nanos()
获取纳秒(ns)级别的时间戳。快速总结:如果你急需获取时间戳且使用Linux或POSIX系统,请直接跳转到下面标题为“millis()
, micros()
, and nanos()
”的部分,并使用这些函数。如果你使用的是C11而不是Linux或POSIX系统,则需要将那些函数中的clock_gettime()
替换为timespec_get()
。
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。
Linux和POSIX:在C中,timespec_get()
比Linux和POSIX函数clock_gettime()
更好,它也可以在Linux或POSIX系统上的C++中正常工作。clock_gettime()
允许您选择所需的时钟。您可以使用clock_getres()
读取指定的时钟分辨率,尽管它也不会给出您的硬件的真实时钟分辨率。相反,它为您提供struct timespec
的tv_nsec
成员的单位。使用我刚才描述并在我的timinglib.c/.h文件中的get_estimated_resolution()
函数来估算分辨率。
因此,如果您在Linux或POSIX系统上使用C语言,我强烈建议使用clock_gettime()
而不是timespec_get()
。
timespec_get()
(可行)和Linux/POSIX的clock_gettime()
(更好):以下是如何使用这两个函数:
timespec_get()
#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;
}
clock_gettime()
-- USE THIS ONE WHENEVER POSSIBLE!
CLOCK_MONOTONIC_RAW
clock, which is best for obtaining timestamps used to time things on your system.CLOCK_REALTIME
, CLOCK_MONOTONIC
, CLOCK_MONOTONIC_RAW
, etc: https://man7.org/linux/man-pages/man3/clock_gettime.3.htmlCLOCK_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.// 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存储库中获取最新版本的代码:
// 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
中。
这些结果是硬件特定的,您在自己的硬件上的结果可能会有所不同。
usleep()
和nanosleep()
的回答——它提醒我需要做#define _POSIX_C_SOURCE 199309L
才能从<time.h>
中引入clock_gettime()
POSIX函数!
_POSIX_C_SOURCE >= 199309L
CLOCK_REALTIME
、CLOCK_MONOTONIC
、CLOCK_MONOTONIC_RAW
等。clock_gettime()
: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.htmlclock_gettime()
: https://linux.die.net/man/3/clock_gettime
timespec_get()
,而不是POSIX clock_gettime()
。https://en.cppreference.com/w/c/chrono/clock说:
在C11中使用timespec_get
clock_gettime()
可以让您选择所需的时钟ID,以获取所需的时钟类型!另请参见这里: ***** https://people.cs.rutgers.edu/~pxk/416/notes/c-tutorials/gettime.htmltimespec_getres()
直到C23才支持,因此更新我的示例以包括在Linux上使用POSIX clock_gettime()
和clock_getres()
函数的示例。我想要知道在给定系统上可以期望多好的时钟分辨率。是毫秒级、微秒级、纳秒级还是其他什么? 供参考,请参阅:
clock_getres()
返回1 ns,但实际分辨率约为14~27 ns,根据我的get_estimated_resolution()
函数在这里:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/c/timinglib.c。请参见以下结果:
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。struct timeval包含两个组件,即秒和微秒。具有微秒精度的时间戳表示自纪元以来的秒数存储在tv_sec字段中,分数微秒存储在tv_usec中。因此,您不能忽略tv_sec并期望得到合理的结果。
如果您使用Linux或*BSD,则可以使用timersub()减去两个struct timeval值,这可能是您想要的。
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 */
};
$ perl -E '$_=1e6*60*60*24*360; say int log($_)/log(2)'
44
这可能是将原始返回值拆分为两个部分的原因之一。
使用无符号长整型(即64位单位)来表示系统时间:
typedef unsigned long long u64;
u64 u64useconds;
struct timeval tv;
gettimeofday(&tv,NULL);
u64useconds = (1000000*tv.tv_sec) + tv.tv_usec;
#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中运行良好。
if(tv.tv_usec<10)
{
printf("00000");
}
else if(tv.tv_usec<100&&tv.tv_usec>9)// i.e. 2digits
{
printf("0000");
}
等等……
tv_usec
并不是指“当前时间的微秒数”,而是指“当前时间的微秒数对1000000取模后的值”。 - ruslik(sec2-sec1)*1000000+(usec2-usec1)
。或者更好的方法是,为了避免秒数溢出的可能性,如果你知道时间周期小于一秒,那么当秒数较小时,只需将其加上1000000。 - ruslik