最原始的取时间的方法大概就是time+localtime了,见代码:
#include#include // gcc -o time_1 time_1.c int main() { time_t tm_now; time(&tm_now);// 或者写成 tm_now = time(NULL); //1.直接打印:1970-1-1,00:00:00到现在的秒数 printf("now time is %ld secondn", tm_now); //2.转换成本地时间,精确到秒 struct tm *p_local_tm ; p_local_tm = localtime(&tm_now) ; printf("now datetime: %04d-%02d-%02d %02d:%02d:%02dn", p_local_tm->tm_year+1900, p_local_tm->tm_mon+1, p_local_tm->tm_mday, p_local_tm->tm_hour, p_local_tm->tm_min, p_local_tm->tm_sec); return 0; }
其中time函数返回的是1970年到现在的秒数,精确到秒。
localtime函数是根据这个秒数和本机的时区,解析出年月日时分秒等信息。
这里特别提醒一点,localtime函数不是多线程安全的,localtime_r才是。
还要特别提醒一点,不要在信号响应函数中使用localtime或localtime_r,程序会卡死!
程序运行结果如下:
2.傻瓜版另一个比较好用的函数是gettimeofday。
相比其他函数,gettimeofday可以精确到微秒,还可以指定时区,性能也还可以,可以满足绝大多数场景,因此叫傻瓜版。
示例代码如下:
#include#include #include // gcc -o time_2 time_2.c int main() { struct timeval tm_now; //1.获取当前时间戳(tv_sec, tv_usec) gettimeofday(&tm_now,NULL); // 第二个参数是时区 //2.转换成本地时间,精确到秒 struct tm *p_local_tm; p_local_tm = localtime(&tm_now.tv_sec) ; printf("now datetime: %04d-%02d-%02d %02d:%02d:%02d.%06ldn", p_local_tm->tm_year+1900, p_local_tm->tm_mon+1, p_local_tm->tm_mday, p_local_tm->tm_hour, p_local_tm->tm_min, p_local_tm->tm_sec, tm_now.tv_usec); // 有微秒时间戳了 return 0; }
运行结果如下:
3.进阶版如果微秒级别的精度还不满足要求,可以尝试下clock_gettime,代码如下:
#include#include #include // gcc -o time_3 time_3.c void print_timestamp(int use_monotonic) { struct timespec tm_now; //1.获取当前时间戳(tv_sec, tv_usec) if(use_monotonic) clock_gettime(CLOCK_MONOTONIC, &tm_now); // 单调时间,屏蔽手动修改时间 else clock_gettime(CLOCK_REALTIME, &tm_now); // 机器时间 //2.转换成本地时间,精确到秒 struct tm *p_local_tm; p_local_tm = localtime(&tm_now.tv_sec) ; printf("now datetime: %04d-%02d-%02d %02d:%02d:%02d.%09ldn", p_local_tm->tm_year+1900, p_local_tm->tm_mon+1, p_local_tm->tm_mday, p_local_tm->tm_hour, p_local_tm->tm_min, p_local_tm->tm_sec, tm_now.tv_nsec); // 有纳秒时间戳了 } int main(int argc, char **argv) { int use_monotonic = 0; int optval = 0; while ((optval = getopt(argc, argv, "Mm")) != EOF) { switch (optval) { case 'M': case 'm': use_monotonic = 1; break; default: break; } } while(1) { print_timestamp(use_monotonic); sleep(1); } return 0; }
运行结果如下:
clock_gettime的第一个参数可以指定一个clock_id参数:
常见的有两个:
1) CLOCK_REALTIME
即普通的时间,跟其他时间函数取出来的时间并无区别,运行效果如上。
2) CLOCK_MONOTONIC
即单调时间,跟系统的启动时间有关,不受手动修改系统时间的影响。
如上图,表示系统已经启动了6 05:47:53(东8区零点是1970-01-01 08:00:00)。
表面上看,这个函数精度不错,功能完备,但却存在一个突出缺点–慢。对于性能敏感的函数,频繁调用会影响性能,这一点我们后面仔细说。
4.专家版专家版本的计时函数有两个突出优点:
- 性能高:绕过内核直接读寄存器,开销很小
- 精度高:时间测量的最小单位是1/CPU频率秒,可达0.3纳秒(假设CPU频率为3GHz)
下面是示例程序:
#include#include #include // for atof #include // for uint64_t // gcc -o time_4 time_4.c //获取CPU频率 uint64_t get_cpu_freq() { FILE *fp=popen("lscpu | grep CPU | grep MHz | awk {'print $3'}","r"); if(fp == nullptr) return 0; char cpu_mhz_str[200] = { 0 }; fgets(cpu_mhz_str,80,fp); fclose(fp); return atof(cpu_mhz_str) * 1000 * 1000; } //读取时间戳寄存器 uint64_t get_tsc() // TSC == Time Stamp Counter寄存器 { #ifdef __i386__ uint64_t x; __asm__ volatile("rdtsc" : "=A"(x)); return x; #elif defined(__amd64__) || defined(__x86_64__) uint64_t a, d; __asm__ volatile("rdtsc" : "=a"(a), "=d"(d)); return (d << 32) | a; #else // ARM架构CPU uint32_t cc = 0; __asm__ volatile ("mrc p15, 0, %0, c9, c13, 0":"=r" (cc)); return (uint64_t)cc; #endif } int main(int argc, char **argv) { uint64_t cpu_freq = get_cpu_freq(); printf("cpu_freq is %lun", cpu_freq); uint64_t last_tsc = get_tsc(); while(1) { sleep(1); uint64_t cur_tsc = get_tsc(); printf("TICK(s) : %lun", cur_tsc - last_tsc); printf("Second(s) : %.02lfn", 1.0 * (cur_tsc - last_tsc) / cpu_freq); last_tsc = cur_tsc; } return 0; }
TSC的全称是Time Stamp Counter,它是一个保存着CPU运转时钟周期数的寄存器,在X86等平台下均有提供(ARM平台下是CCR-Cycle Counter Register)。
通过专门的rdtsc汇编指令,可绕过操作系统内核直接从寄存器中读取数值,因此速度极快。
通过上述的get_tsc函数可以从这个寄存器中读出一个64位的数值,连续两次读取的值的差值,即是连续两次调用之间CPU运行的周期数。用这个周期数除以CPU运行的频率(通过上面的get_cpu_freq函数获得),即可得到具体的秒数。
上述代码运行效果如下:
可以看到,我测试用的机器的CPU频率是2.9Ghz的,我每sleep一秒输出一下两次CPU计数器的差值,发现跟频率也能对的上。
事实上,上面的所有取时间的函数,都是基于底层的类似rdtsc指令封装的,我们直接使用最底层的命令,固然快且精确,但是也不可避免的要直面一些坑。
比如我们可能碰见多CPU问题、多线程问题、进程上下文切换问题,计算机主动调节CPU频率问题等。为了顺利地使用这个指令,我们就要对程序和操作系统做一系列的限制,比如rdtsc的结果不在CPU间共享、进程运行时绑定CPU以避免被切换到另外的CPU上去、禁止计算机主动调频功能等。
5.关于性能我们写了一个测试程序,跑10亿次,取平均时间,分别测试几个函数的性能:
#include#include #include #include #include #include // gcc -o time_5 time_5.c uint64_t get_by_time() { time_t tm_now; time(&tm_now); return tm_now; } uint64_t get_by_gettimeofday() { struct timeval tm_now; gettimeofday(&tm_now,NULL); return tm_now.tv_sec; } uint64_t get_by_clock_gettime() { struct timespec tm_now; clock_gettime(CLOCK_REALTIME, &tm_now); return tm_now.tv_sec; } uint64_t get_cpu_freq() { FILE *fp=popen("lscpu | grep CPU | grep MHz | awk {'print $3'}","r"); if(fp == NULL) return 0; char cpu_mhz_str[200] = { 0 }; fgets(cpu_mhz_str,80,fp); fclose(fp); return atof(cpu_mhz_str) * 1000 * 1000; } uint64_t get_by_tsc() { uint64_t a, d; __asm__ volatile("rdtsc" : "=a"(a), "=d"(d)); return (d << 32) | a; } void print_diff(uint64_t loop_times, uint64_t beg_tsc, uint64_t end_tsc) { double tt_ns = (end_tsc - beg_tsc) * 1.0 * 1000 * 1000 * 1000 / get_cpu_freq(); printf("Number Loop : %lun", loop_times); printf("Total Time : %.02lf nsn", tt_ns); printf("Avg Time : %.02lf nsn", tt_ns / loop_times); } #define LOOP_TIMES 1000000000 int main(int argc, char **argv) { uint64_t beg_tsc, end_tsc; long loop; printf("-------------time()-------------n"); loop = LOOP_TIMES; beg_tsc = get_by_tsc(); while(loop--) get_by_time(); end_tsc = get_by_tsc(); print_diff(LOOP_TIMES, beg_tsc, end_tsc); printf("-------------gettimeofday()-------------n"); loop = LOOP_TIMES; beg_tsc = get_by_tsc(); while(loop--) get_by_gettimeofday(); end_tsc = get_by_tsc(); print_diff(LOOP_TIMES, beg_tsc, end_tsc); printf("-------------clock_gettime()-------------n"); loop = LOOP_TIMES; beg_tsc = get_by_tsc(); while(loop--) get_by_clock_gettime(); end_tsc = get_by_tsc(); print_diff(LOOP_TIMES, beg_tsc, end_tsc); printf("-------------rdtsc-------------n"); loop = LOOP_TIMES; beg_tsc = get_by_tsc(); while(loop--) get_by_tsc(); end_tsc = get_by_tsc(); print_diff(LOOP_TIMES, beg_tsc, end_tsc); return 0; }
测试结果如下:
可以看到:
- time函数最快,但是精度太低
- gettimeofday和clock_gettime虽然精度高,但是都比较慢
- rdtsc精度和速度都十分优秀
另外需要注意一点的是,上述测试结果跟机器配置有很大关系,我测试所用的机器是一台ubuntu虚拟机,CPU只有2.9GHz。



