在之前的文章【C/C++】日常时间的获取、格式化等操作汇总中讲到的时间都是以秒为精度的。有时候需要确定某个事件的准确时间,就得把精度提高到毫秒(millsecond)、微秒(microsecond)、纳秒(nanosecond,很少用)。这里将讲解并演示如何使用。

C语言中所支持的精度在秒之下有微秒和纳秒,所以需要精度为毫秒的话建议直接使用微秒这个更高精度的时间值。

文章结构:


高精度时间结构体

  • 微秒
    #include <sys/time.h>
    struct timeval {
        time_t      tv_sec;     /* seconds */
        suseconds_t tv_usec;    /* microseconds */
    };
    
    在结构体中,tv_sec表示当时时刻的秒数,和之前文章提到的日常时间或时间绝对值是等价的。tv_usec则是微秒值。即1000000微秒为1秒,所以其取值范围为[0, 1000000)。
    由于tv_sectime_t的结构体,所以很容易获取当前时间的详情或格式化成时间文本。见之前的文章【C/C++】日常时间的获取、格式化等操作汇总
  • 纳秒
    #include <time.h>
    struct timespec {
        time_t   tv_sec;        /* seconds */
        long     tv_nsec;       /* nanoseconds */
    };
    

在结构体中,tv_sec也表示当时时刻的秒数。tv_nsec则是纳秒值。即10亿纳秒为1秒,所以其取值范围为[0, 10^12)。


时间函数

  • 获取微秒:

    int gettimeofday(struct timeval *tv, struct timezone *tz);

执行本函数后,所获取的时间值将会被存储在第一个参数tv中,第二参数tz在Linux中已经不被使用了(自己搜索夏令时),所以直接传NULL
函数正常执行后将会返回0,否则返回-1并可通过errno来查看失败原因。
如果系统管理员修改了机器的时间(调整日期),则本函数所获取的时间也会随之发生变化。

  • 获取纳秒:
    获取纳秒的步骤比较麻烦,需要提前获取进程id,和clock_id

获取进程id的函数如下:

#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);

getpid()返回当前进程的进程id,getppid()返回当前进程的父进程的进程id。

#include <time.h>

int clock_getcpuclockid(pid_t pid, clockid_t *clock_id);

获取指定进程的clock_id。如果参数pid赋值为0,则获取的是当前函数执行的进程的clock_id
当函数正常执行时返回0,且clock_id会被正确赋值;出错时则返回ENOSYS(内核版本不支持)、EPERM(权限不够)、ESRCH(找不到id值为pid的进程)。

#include <time.h>

int clock_gettime(clockid_t clk_id, struct timespec *tp);

这才是最后的最后,获取纳秒。
gettimeofday()不同的是clock_gettime()是获取进程的时间所经过的时间,所以不随系统管理员修改机器时间而改变。
一般用不到纳秒,本文章不预继续讨论了。(我会跟你说其实是我懒得写了! (逃… )


宏:计算时间差

获取到微秒的时间后,由于时间被分开成了两个值来表示,所以时间差的计算并不能直接相减。
查看time.h发现已经有一些现成长定义好的宏可以用来计算时间差

1
2
3
4
5
6
7
8
9
#define timersub(tvp, uvp, vvp) \
do { \
(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
if ((vvp)->tv_usec < 0) { \
(vvp)->tv_sec--; \
(vvp)->tv_usec += 1000000; \
} \
} while (0)

被减数tvp 减去 uvp的结果存储于vvp中。
还有一些其它的宏,如:

1
2
3
4
timerclear // 清除时间
timerisset // 结构体中是否有设置时间值
timercmp // 比较时间值大小
timeradd // 两个时间值相加


示例代码

以下代码演示获取微秒时间 ,并转换为日常时间的格式化字符串,最后求得本段代码在运行时的总耗时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct timeval t_val;
gettimeofday(&t_val, NULL);
printf("start, now, sec=%ld m_sec=%d \n", t_val.tv_sec, t_val.tv_usec);
long sec = t_val.tv_sec;
time_t t_sec = (time_t)sec;
printf("date:%s", ctime(&t_sec));
struct timeval t_val_end;
gettimeofday(&t_val_end, NULL);
struct timeval t_result;
timersub(&t_val_end, &t_val, &t_result);
double consume = t_result.tv_sec + (1.0 * t_result.tv_usec)/1000000;
printf("end.elapsed time= %fs \n", consume);

运行结果:

time.micro.demo