ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux C/C++时间获取:time与gettimeofday的用法及选型

Linux C/C++时间获取:time与gettimeofday的用法及选型 写日志、打时间戳、统计耗时是Linux下每个C/C开发都会遇到的事。刚开始接触Linux编程那会儿我获取系统时间只会用time函数后来看到同事代码里的gettimeofday才意识到不同场景对时间精度的要求完全不一样。这篇文章就围绕这两个最常用的函数把原理、用法、选型思路和我在实际项目中踩过的坑一次性讲透适合刚入门的Linux开发者、嵌入式工程师以及任何想在代码里正确处理时间的小伙伴。1. 两块“表”能干什么time 与 gettimeofday 的定位1.1 从日常需求看时间获取在Linux下开发获取系统时间几乎是绕不开的操作。比如服务端程序要往日志里写一条记录必须带上“2025年X月X日 XX:XX:XX”这样的时间前缀压测工具要统计某个接口处理花了多少毫秒数据库要生成一条带时间戳的数据甚至rand()取随机数之前大家也都习惯用时间来做种子。这些需求看起来都是“拿时间”但细细分下来精度要求差异很大。写日志用秒级就够了记录到哪一秒完全没问题可要是测量一段代码的执行性能秒级就太粗糙了你需要的是毫秒甚至微秒级别的精度。正是因为这个差异Linux下才会有time和gettimeofday这两个经常被放在一起讨论的函数。简单理解就是time负责给你“秒”gettimeofday给你“秒加微秒”。它们底层都依赖内核维护的系统时间但从使用方式、返回的数据结构、适用的场景来看区别还是很明显的。1.2 time 的定位与适用场景time函数的定位就是获取当前时间对应的Unix时间戳单位是秒。它返回的是一个time_t类型的长整型数值这个数值表示从1970年1月1日0时0分0秒UTC到当前时刻经过的秒数这个起点在计算机领域被称为“Unix纪元”Unix Epoch。它的适用场景我总结了一下主要有这么几类日志记录。给日志行加一个秒级的时间戳前缀完全够用。日期计算。比如判断一个时间戳是否已过期、计算两个时间点相差多少天。生成随机数种子。给srand传一个变化足够快的值秒级也够了。做数据快照时标记数据的最后修改时间。说句实在话很多初级开发者一开始容易陷入一个误区觉得“时间这个值越精确越好”所以在所有场景里都上gettimeofday。但实际上精度越高意味着底层调用的复杂度和潜在坑也越多。如果只是给日志加个时间戳用秒级的time不仅代码简洁也完全满足需求。1.3 gettimeofday 的定位与适用场景gettimeofday解决的问题是time给不了的高精度场景。它能够返回当前时间精度理论上可以达到微秒microsecond百万分之一秒级别。需要用到微秒级时间的场景典型的有这些性能测试。你写了一个排序算法想比较不同数据量下的耗时微秒级的计时才能看出差异。帧率控制。在图形程序或游戏逻辑里计算每帧间隔毫秒和微秒级的计时能提供更平滑的控制。数据采集打时标。传感器采集程序需要给每个数据点打上精确的时间标记。生成短时间内不会重复的序列号或ID的一部分。如果你要做的是测量“这段代码跑了多久”那gettimeofday可以在绝大多数场景下帮你完成任务。2. time 函数深度拆解低精度但够用2.1 函数声明与原理解读先看time函数的原型#include time.h time_t time(time_t *tloc);time_t在不同的平台上实现不同在Linux下通常是一个long类型的整数。它本质上就是一个整数计数器内核在启动后按照系统维护的墙上时钟wall clock不断更新这个秒数。每一次调用time返回值就是当前这个计数器的快照。有个有意思的点是这个值在32位系统上是一个32位的整数。32位有符号整数的最大值是2147483647换算成时间便是2038年1月19日03:14:07UTC。再过一秒钟这个数就会溢出变成负数。这个问题在嵌入式领域尤其需要警惕后面第5节我会专门展开讲。从内核角度看time函数最终会触发一个系统调用但在glibc的实现里由于内核会维护一个由内核定时器周期性更新的“快速时间变量”这个系统调用可以被优化成直接从内存读取数据所以它的开销非常小甚至在很多场景下可以认为是接近零成本的。正因为这样你在循环里高频调用time都不用太担心性能问题。2.2 参数 tloc 的两种用法time函数带了一个指针参数tloc这个参数的设计初看有点多余但理解它的两种用法能帮你读懂很多老的C代码。第一种用法是传入NULL这样函数只通过返回值把时间戳交给你time_t now time(NULL); printf(current seconds: %ld\n, (long)now);第二种用法是传入一个time_t类型变量的地址函数会把结果同时写到这个变量里并且依然返回同样的值time_t now; time(now); printf(current seconds: %ld\n, (long)now);从功能上讲这两种写法结果完全一样。我个人的习惯是第一种因为代码看起来更直观少写一行。不过有一点需要提醒在阅读老代码时经常会看到time(now);这种写法它并不代表什么高级用法只是作者的个人风格或者历史习惯知道它的等价关系就可以了。2.3 结构化输出localtime / gmtime / strftime 配套使用time拿到的是time_t一个很大的数字例如1785456000。这样的数字给人看完全没概念。你需要把它转成人类可读的结构化时间。这里就要引出三个常用的配套函数struct tm *localtime(const time_t *timep); struct tm *gmtime(const time_t *timep); size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);localtime会把时间戳转换成本地时区对应的年月日时分秒返回一个struct tm结构体指针gmtime则转换成UTC时区对应的结构体。struct tm结构体定义在time.h里成员包括年、月、日、时、分、秒、星期等struct tm { int tm_sec; // 秒范围 0-6060 表示闰秒 int tm_min; // 分范围 0-59 int tm_hour; // 时范围 0-23 int tm_mday; // 日范围 1-31 int tm_mon; // 月范围 0-11注意 0 代表一月 int tm_year; // 年从 1900 年起的年数 int tm_wday; // 星期范围 0-60 代表周日 int tm_yday; // 一年中的第几天范围 0-365 int tm_isdst; // 夏令时标志 };这里有两个特别容易踩坑的地方一个是tm_mon从0开始1月是02月是1直接用tm_mon打印月份会出现差一个月的怪问题另一个是tm_year是从1900年开始计算的要拿到实际年份需要加1900。很多打印出“1970年”的bug十有八九就是漏掉了这个偏移量。拿到struct tm之后最灵活的输出方式是strftime。它类似于printf通过格式化占位符把时间拼成任何你想要的字符串格式。常用格式有%Y四位年份如2025%m两位月份01-12%d两位日期01-31%H24小时制小时00-23%M分钟00-59%S秒00-59%z时区偏移如08002.4 带格式的取时间示例下面这段代码展示了从time到格式化输出的完整流程包括本地时间和UTC时间两种打印方式#include stdio.h #include time.h int main(void) { time_t now time(NULL); struct tm tm_local; struct tm tm_utc; localtime_r(now, tm_local); gmtime_r(now, tm_utc); char buf_local[128]; char buf_utc[128]; strftime(buf_local, sizeof(buf_local), %Y-%m-%d %H:%M:%S, tm_local); strftime(buf_utc, sizeof(buf_utc), %Y-%m-%d %H:%M:%S, tm_utc); printf(local time: %s\n, buf_local); printf(utc time : %s\n, buf_utc); return 0; }注意我在这里用了localtime_r和gmtime_r而不是localtime和gmtime。原因很简单localtime和gmtime返回的是静态存储区的指针在多线程环境下会被覆盖这是非常经典的一个坑。带_r后缀的版本把结果写入调用者提供的缓冲区线程安全是实际开发中的推荐写法。关于线程安全第5节还会专门说明。3. gettimeofday 函数深度拆解微秒级计时的利器3.1 函数声明与 struct timeval 结构体接下来是重头戏gettimeofday。它的函数原型如下#include sys/time.h int gettimeofday(struct timeval *tv, struct timezone *tz);这里涉及一个关键结构体struct timeval定义如下struct timeval { time_t tv_sec; // 秒 suseconds_t tv_usec; // 微秒 };tv_sec和time函数的返回值一样是从Unix纪元开始经过的秒数tv_usec是当前这一秒内已经过去的微秒数范围是0到999999。两者相加就得到了一个“秒.微秒”形式的时间值。第二个参数tz是时区相关的旧接口用来获取时区偏移和夏令时标志。在POSIX标准里已经明确不推荐使用glibc手册里也写得很清楚这个参数是为了兼容历史代码才保留的实际使用中直接传NULL即可。调用示例struct timeval tv; gettimeofday(tv, NULL); printf(seconds: %ld, microseconds: %ld\n, (long)tv.tv_sec, (long)tv.tv_usec);一个需要特别留意的点是tv_usec是“当前秒内的微秒数”不是一个完整的时间戳。如果要用微秒为单位表示当前时间正确的计算方式是long long timestamp_us (long long)tv.tv_sec * 1000000 tv.tv_usec;先乘后加不要直接用tv_usec当微秒时间戳。3.2 为什么它号称能到微秒级很多人会有疑问time返回的是秒gettimeofday怎么能做到微秒这背后是内核时间子系统的设计。Linux内核维护的不是一个简单的“秒计数器”而是一个高精度的时钟源。在x86平台上内核会结合硬件定时器和时钟芯片来维护一个纳秒级别的系统时间。当用户态调用gettimeofday时内核会基于当前系统时间计算出现在是多少秒、多少微秒然后填到struct timeval里返回给用户态。这里必须澄清一个概念gettimeofday的“精确到微秒”是指时间戳数值可以精确到微秒但实际的精度还取决于系统时钟源、硬件定时器的中断频率和虚拟化平台等因素。也就是说在大部分普通Linux桌面和服务器上你调用它拿到的微秒数字是可信的但在某些虚拟化环境或者老式嵌入式硬件上实际精度可能只有毫秒级只是数值上最后几位凑出来的。另外gettimeofday本身是一次系统调用存在用户态和内核态的切换开销。单次调用的耗时一般在几百纳秒到几微秒的量级如果你在性能测试里把gettimeofday放在被测代码内部循环里会有一定的测量扰动需要在设计时考虑到这一点。3.3 经典用法精确计算程序耗时gettimeofday最经典的用法就是秒表式计时开始时取一个时间点结束时再取一个时间点两者相减得到耗时。网上很多示例代码是这样写的struct timeval start, end; gettimeofday(start, NULL); // 这里是待测代码 gettimeofday(end, NULL); long cost_us (end.tv_sec - start.tv_sec) * 1000000 (end.tv_usec - start.tv_usec);这个写法本身没问题但有几个细节建议优化。第一个不要直接比较tv_sec和tv_usec的绝对值因为tv_usec在跨秒时会从999999跳回0直接减容易出错。正确的做法是统一换算成微秒再相减也就是上面的公式。第二个如果耗时可能超过2^31微秒约35.8分钟建议用long long类型保存结果避免溢出。下面是一个更完整的计时工具示例计算一个循环的总耗时和每次迭代的平均耗时#include stdio.h #include sys/time.h static long long now_us(void) { struct timeval tv; gettimeofday(tv, NULL); return (long long)tv.tv_sec * 1000000 tv.tv_usec; } int main(void) { long long start now_us(); volatile int sum 0; for (int i 0; i 1000000; i) { sum i; } long long end now_us(); long long total_us end - start; printf(total time: %lld us (%.3f ms)\n, total_us, total_us / 1000.0); printf(average per iteration: %.3f ns\n, total_us * 1000.0 / 1000000); return 0; }封装一个now_us函数把struct timeval的转换逻辑隐藏掉主流程看起来就清爽很多。这里加volatile是为了防止编译器把空循环优化掉否则可能测出一个几乎为0的耗时这也是性能测试中常见的一个坑。3.4 什么时候不选它和 clock_gettime 的取舍讲了这么多gettimeofday的好必须说一个现实情况在较新的POSIX标准里gettimeofday已经被标记为过时obsolete接口推荐替代方案是clock_gettime。clock_gettime支持多种时钟源其中两个最常用CLOCK_REALTIME和gettimeofday一样表示墙上时钟受系统时间调整影响。CLOCK_MONOTONIC单调时钟不受NTP跳变或手动改时间影响只增不减专门用来测量时间间隔。clock_gettime用的结构体是struct timespec精度到了纳秒级别struct timespec { time_t tv_sec; // 秒 long tv_nsec; // 纳秒 };所以在测量耗时、计算时间间隔这类场景下我更推荐直接使用clock_gettime(CLOCK_MONOTONIC, ts)。它的好处是无论系统时间怎么被调整计时结果都不受影响这对性能测试的稳定性很关键。不过gettimeofday在现实代码中的存量非常大网上大量开源项目、老教程都还在用它而且它足够简单处理绝大多数场景已经够了。所以我的建议是存量代码里看到gettimeofday不用急着改能用就行新写的耗时测量代码优先考虑clock_gettime(CLOCK_MONOTONIC)只是记录一个时间戳给日志用那就直接用time。4. 对比与选型两个函数什么时候用哪个4.1 一张表格看明白核心差异为了让你一眼看清楚我把两个函数的核心差异整理成了表格对比项timegettimeofday头文件time.hsys/time.h返回时间精度秒微秒关键类型/结构体time_tstruct timeval返回值time_t 时间戳0表示成功-1表示失败典型用途日志时间戳、日期计算、随机数种子耗时统计、高精度时标是否受时区影响本身是UTC秒数显示时受时区影响本身是UTC秒数显示时受时区影响是否受系统时间调整影响受影响时间可能跳动受影响时间可能跳动线程安全性time本身安全localtime/ctime不安全本身安全需注意结构体归属底层调用开销很小可能直接读内存相对大有系统调用开销标准状态标准且常用POSIX标记为过时仍广泛使用这张表基本覆盖了选型时需要考虑的关键因素。最简单的记忆方式是只要精度要求没超过秒就用time要求微秒级才用gettimeofday。4.2 开销、精度与时区这些隐藏因素很多初学者容易忽略性能开销这个维度。time在主流glibc实现中甚至可能不触发真正的系统调用因为内核为它维护了一个vdsoVirtual Dynamic Shared Object映射用户态可以直接读取时间数据几乎零成本。而gettimeofday在多数平台上也走了vdso优化性能很好但它返回的数据结构更复杂毕竟要填充秒和微秒两个字段。这里简单解释一下vdso。为了减少频繁获取时间时的系统调用开销内核把一段精心设计的代码映射到用户进程的地址空间用户态调用gettimeofday时实际上可能直接执行这段映射代码从内核共享的内存页里读取时间信息而不需要陷入内核。所以实际开销比很多人想象中要小得多。但在高频率轮询比如每微秒调一次的场景下即使有vdso仍然会有缓存一致性和计算开销设计时还是要尽量批量获取时间而不是在循环里频繁调用。时区问题也值得强调。time和gettimeofday返回的都是UTC标准下的秒数本身不带任何时区信息。时区只是在你把它转换成struct tm并显示时才参与到计算中。localtime系列会通过读取系统的TZ环境变量或/etc/localtime配置来转换时区。如果你的服务器时区配置错了即使底层时间戳完全正确打印出来的日志时间也会跟着错。所以在存储和传输时间时最佳实践是存time_t这种绝对秒数只在给人看的时候转成本地时间字符串。这样无论部署到哪个时区的机器上时间逻辑都不会乱。4.3 可移植性和嵌入式环境的注意点跨平台方面time是C标准库函数所有主流平台都支持可移植性最好。gettimeofday是POSIX标准里的接口在Linux、macOS、BSD上都能用但在Windows原生环境下没有这个函数如果代码要跨Windows编译需要做条件编译或者改用Windows API。在嵌入式Linux领域情况要复杂一些。如果你的程序跑在完整的Linux用户空间time和gettimeofday都可以正常用。但如果你在写内核驱动或者嵌入式裸机程序这两个函数都不能直接用内核驱动里应该用ktime_get_real_ts64这类内核API裸机环境则要依赖芯片自带的定时器外设。很多从裸机转Linux开发的工程师经常会下意识地在驱动里调用用户态的gettimeofday然后发现编译都过不了这个需要特别注意。另一个嵌入式环境常见的问题是有些裁剪过的内核或者精简libc库可能没有实现gettimeofday的vdso优化这时候每次调用都是完整系统调用性能影响会比普通桌面系统大很多高精度计时时要考虑到这一层开销。5. 实操过程中踩过的坑经验之谈5.1 time_t 回绕问题2038年不是玩笑前面提到过time_t在32位系统上是32位有符号整数最大能表示到2038年1月19日03:14:07 UTC。在这之后再调用time函数返回值会变成一个负数程序里所有基于时间的判断、日志打印、过期校验都会崩坏。这个坑在传统的32位ARM嵌入式设备上尤其危险因为很多IoT硬件还在用32位MCU和32位Linux系统。如果你的产品计划在2038年之后继续运行现在写的代码就应该考虑这个问题。办法主要有几个编译时加上-D_TIME_BITS64配合-D_FILE_OFFSET_BITS64强制把time_t变成64位。检查你使用的libc版本和内核版本是否支持64位time_t老版本glibc可能要升级。设计存储协议时不用裸的time_t而是自定义64位整数来存时间戳比如统一的long long类型。实际工作中我建议凡是新写的代码涉及到存时间戳的字段一律用long long或int64_t不要直接依赖time_t的宽度。这样即使换了平台或者编译选项数据结构也不会因为time_t宽度不同而出问题。5.2 时区导致的日志时间偏移这个坑我记忆犹新。有一次排查线上问题发现服务日志的时间和真实时间差了整整8个小时客户反馈说日志里的时间线完全对不上。那时候第一反应是代码bug查了半天才发现是服务器系统时区没设置好/etc/localtime指向了UTC。在Linux下time函数本身拿到的UTC秒数是没错的但只要转换成显示字符串就和系统时区强相关。排查这个问题有几个关键命令date # 查看当前系统时间和时区 timedatectl # 现代systemd系统上查看和设置时区 cat /etc/timezone # Debian/Ubuntu上查看时区配置 cat /etc/localtime # 查看符号链接指向要稳妥地处理时区问题我总结了三条经验服务器统一设置成UTC日志里明确标注时区这样多台机器比对日志时最省心。如果业务要求显示本地时间优先用tm_isdst字段配合tzset()函数正确初始化时区解析。代码里不要自己写“UTC8”这种时区偏移计算逻辑直接交给localtime全家桶处理手动加减小时会在夏令时地区翻车。5.3 gettimeofday 时间跳跃NTP 和 date -s 的杀伤力用gettimeofday测量耗时最隐蔽的坑是系统时间被调整。最常见的触发源有两个一个是NTP自动同步另一个是运维手动执行date -s修改时间。NTP发现本地时间和标准时间偏差过大时会直接“跳变”校准而不是慢慢微调。如果程序正好在这一瞬间用gettimeofday做耗时统计测出来的时间差可能是一个负数或者一个离谱的巨大值。我自己就遇到过一次性能测试报告里出现耗时-3秒的情况查到最后就是NTP同步引起的。关键点在于gettimeofday读取的是墙上时钟wall clock它的本质是“现在是什么时间”而不是“过去了多少时间”。系统时间被人为改动时它根本无法区分到底是正常的时间流逝还是被跳变了。解决办法也很明确测量时间间隔换用clock_gettime(CLOCK_MONOTONIC)。单调时钟从系统启动开始计数不受NTP和人工改时的影响只反映真实流逝的时间。例如struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, start); // 待测代码 clock_gettime(CLOCK_MONOTONIC, end); long long diff_ns (end.tv_sec - start.tv_sec) * 1000000000LL (end.tv_nsec - start.tv_nsec);这个写法在长时间运行的服务里非常重要。你的服务可能连续跑几个月期间一定会发生NTP同步用墙上时钟计时等于给自己埋雷。5.4 线程安全localtime 与 ctime 的隐藏坑现代服务端程序基本都是多线程的时间相关的线程安全问题是我排查过最多的一类诡异bug。localtime、gmtime、ctime、asctime这几个函数返回的都是指向静态存储区的指针。所谓静态存储区就是整个程序只有一份、所有线程共享的内存。线程A调用localtime拿到了指针还没来得及打印线程B也调用了localtime就把这份内存里的内容覆盖了。结果线程A打印出来的时间可能跟它自己的时间点完全对不上。多线程下正确做法是用带_r后缀的可重入版本struct tm *localtime_r(const time_t *timep, struct tm *result); char *ctime_r(const time_t *timep, char *buf);它们把结果写入调用者提供的缓冲区互不干扰。下面是一个实际项目中我常用的线程安全日志时间封装void get_log_time(char *buf, size_t len) { time_t now time(NULL); struct tm tm_now; localtime_r(now, tm_now); strftime(buf, len, %Y-%m-%d %H:%M:%S, tm_now); }在所有需要打日志的模块里调用这个函数不需要考虑锁的问题也基本不存在性能瓶颈。如果你看到老代码里在多线程环境下直接用localtime那这个程序迟早会在某个高并发时刻输出混乱的时间。5.5 中断和驱动里的时间获取限制这个问题主要涉及嵌入式Linux和内核开发。在用户空间里获取时间是个再普通不过的操作。但进入内核态或者驱动上下文之后情况就不一样了。内核驱动中不能直接用用户态的time或gettimeofday因为依赖关系不对。内核提供了自己的时间接口比如ktime_get_real_ts64(struct timespec64 *ts)获取墙上时钟时间。ktime_get_ts64(struct timespec64 *ts)获取单调时钟时间。jiffies变量内核心跳计数适合粗略的时间判断。在中断上下文里还要注意不能调用任何可能引起睡眠的函数比如获取时间时如果涉及锁等待可能导致系统挂死。多数内核时间接口是考虑过这个问题的但如果你调了一个会分配内存、拿信号量的辅助函数就有可能导致死锁或者严重调度延迟。做驱动的朋友一定要建立这个意识你不是在写普通的用户态程序内核API的使用限制比用户态严格得多时间获取也不例外。最简单的方式是在驱动里需要时间戳时先查一下对应内核版本头文件里推荐的API不要凭用户态编程的经验来猜。6. 拿来即用的完整代码日志时间与耗时测量6.1 秒级日志时间戳time localtime_r strftime下面这个封装函数可以直接放进你的工具库给日志模块打时间戳用。它做成了线程安全版本也考虑到了strftime缓冲区大小的问题#include stdio.h #include string.h #include time.h void format_current_time(char *buf, size_t size) { if (buf NULL || size 0) { return; } time_t now time(NULL); struct tm tm_now; localtime_r(now, tm_now); strftime(buf, size, %Y-%m-%d %H:%M:%S, tm_now); } int main(void) { char time_buf[64]; format_current_time(time_buf, sizeof(time_buf)); printf(log time: %s\n, time_buf); return 0; }这个函数有几个设计细节值得说一下。第一统一使用localtime_r而不是localtime多线程安全第二允许调用者传入缓冲区大小避免越界第三返回值通过指针传递不依赖静态缓冲区灵活性高。如果你的日志需要做到毫秒级区分可以在这个基础上把gettimeofday的微秒字段拼上去#include stdio.h #include sys/time.h #include time.h void format_current_time_ms(char *buf, size_t size) { if (buf NULL || size 0) { return; } struct timeval tv; gettimeofday(tv, NULL); struct tm tm_now; localtime_r(tv.tv_sec, tm_now); strftime(buf, size, %Y-%m-%d %H:%M:%S, tm_now); size_t len strlen(buf); snprintf(buf len, size - len, .%03ld, (long)tv.tv_usec / 1000); }这样做的好处是既保持了秒级部分的易读性又能通过毫秒字段区分同一秒内的多条日志顺序对排查性能问题特别有用。6.2 微秒级耗时测量gettimeofday 封装宏做性能测试时经常要对某段代码反复计时。写成一个宏或者封装函数代码会整洁很多。下面这种封装方式是很多项目里常见的做法#include stdio.h #include sys/time.h #define TIME_COST_US(expr) do { \ struct timeval _start, _end; \ gettimeofday(_start, NULL); \ do { expr; } while (0); \ gettimeofday(_end, NULL); \ long long _cost (_end.tv_sec - _start.tv_sec) * 1000000LL \ (_end.tv_usec - _start.tv_usec); \ printf(cost: %lld us\n, _cost); \ } while (0) void test_function(void) { volatile int sum 0; for (int i 0; i 100000; i) { sum i * 2; } } int main(void) { TIME_COST_US(test_function()); return 0; }使用宏而不是函数来包住被测试代码是因为宏能保留调用现场用法更灵活可以测试任意表达式、函数调用或代码块。do { ... } while (0)这个写法是为了让宏在if、for等控制结构里安全使用避免分号或作用域问题。如果你要测量的代码耗时非常短比如纳秒级别的单指令操作那gettimeofday本身的调用开销会污染测量结果这时候应该把被测代码放进循环里跑很多次用总耗时除以次数得到单次平均耗时。这也是为什么很多benchmark工具都有“预热”和“多次迭代”的阶段。从工程实践角度看处理Linux系统时间并不是背两个函数原型那么简单核心是理解精度需求、时区语义、线程安全这三件事。日志打点优先用time加localtime_r加strftime高精度测量优先用clock_gettime(CLOCK_MONOTONIC)存量代码里的gettimeofday可以继续用但要明白它的局限。我自己这几年的体会是真正困难的问题往往不是“怎么获取时间”而是“获取到时间之后怎么保证它不给你挖坑”存储和传输时间始终坚持用UTC秒数或64位时间戳不要存“格式化后的字符串”。多线程代码里永远不要用localtime、ctime强烈建议统一使用_r版本。凡是测量时间间隔一律用单调时钟不要用墙上时钟。涉及时间戳溢出的场景尽早使用64位类型不要赌自己的程序活不过2038年。把这几点记牢你在Linux下和时间打交道的基本功就算扎实了。如果后续有什么更深入的问题欢迎留言交流。
返回列表