资讯动态

C/C++程序运行时间测量全指南:从clock()到steady_clock的坑与解法

发布时间:2026/10/5 1:55:22 来源:尧图企业网站定制
很多人第一次接触“C/C 计算程序运行的时间”时以为就是调个clock()然后除以CLOCKS_PER_SEC完事。等你真去测一个复杂算法的耗时或者想对比两个函数实现的性能时会发现结果一会儿一个样甚至出现运行时间越优化越长的怪事。这篇文章我就把C/C计时这件事从头到尾捋一遍从最基础的API到高精度计时器再到多线程和编译器优化带来的坑一次性讲透。这篇内容适合几类人刚学完C/C语法、想量化自己算法效率的学生工作中需要做性能基准测试的开发者以及在Linux和Windows跨平台环境下折腾计时功能的工程师。读完之后你不仅能写出正确的计时代码还能理解为什么有时候测出来的时间不准确以及如何设计一个可信的测试方案。1. 计时API盘点从clock()到steady_clock各自的水有多深先说结论C/C标准库提供的计时接口不少但真正能用在性能测试上的并没有想象中那么多。很多接口的精度、语义和平台行为和名字看起来的完全不同。1.1clock()并不测量“运行时间”clock()可能是新手最常用的计时函数它的返回值是“处理器时间”不是墙钟时间wall-clock time。这两个概念的区别非常关键墙钟时间从程序开始到结束真实世界流逝的时间。墙上挂钟走了多少秒就是多少秒。处理器时间CPU实际花在你这程序上的时间。如果程序在等待I/O比如读磁盘、网络请求或者被操作系统调度出去让其他进程运行这段时间不会计入clock()的返回值。在Linux下clock()返回的是进程消耗的CPU时间单位是CLOCKS_PER_SEC通常也是1,000,000微秒级。在Windows的MSVC实现里clock()返回的则是墙钟时间。同一个函数两个平台两个语义如果你拿它做跨平台性能对比数据根本不可信。用clock()测纯计算型的算法比如排序、矩阵乘法问题不大因为这类程序基本一直在跑CPU。但一旦涉及文件读取、网络通信、多线程并行clock()的结果就会严重偏离真实体验。#include cstdio #include ctime #include thread #include chrono int main() { clock_t start clock(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟I/O等待 clock_t end clock(); // Linux下这个值接近0Windows下这个值接近100ms double elapsed static_castdouble(end - start) / CLOCKS_PER_SEC * 1000.0; std::printf(clock() elapsed: %.2f ms\n, elapsed); return 0; }我见过不少人在算法题、课程设计里用clock()测排序耗时这通常能work。但如果拿到生产环境做性能分析尤其是有sleep、锁等待、I/O的场景clock()会给你一个“过于乐观”的数字。记住clock()测的是CPU忙了多久不是用户等了多久。1.2 Windows下的老牌计时器GetTickCount与QueryPerformanceCounter在Windows平台上有两个经典的Windows API经常被用来计时。GetTickCount()返回系统启动至今的毫秒数精度大约在10~16毫秒分辨率粗糙偶尔还会因为系统时钟调整出现跳变。用来测一个耗时几秒的操作没问题测毫秒级别就像用米尺量头发丝。更可靠的是QueryPerformanceCounter()简称QPC和QueryPerformanceFrequency()。QPC是Windows提供的高分辨率计时器底层通常直接映射到硬件计数器比如TSC或HPET精度可以达到微秒甚至纳秒级别是Windows下做性能测试的首选。#include windows.h #include cstdio double now_ms() { LARGE_INTEGER freq, counter; QueryPerformanceFrequency(freq); // 每秒计数个数 QueryPerformanceCounter(counter); // 当前计数 return static_castdouble(counter.QuadPart) / freq.QuadPart * 1000.0; } int main() { double start now_ms(); // 被测代码... double end now_ms(); std::printf(QPC elapsed: %.3f ms\n, end - start); return 0; }QPC有个需要注意的地方在多核CPU上早期实现里不同核心的计数器可能不同步导致线程在不同核心间迁移时读到异常值。现代Windows系统已经通过时钟中断校准规避了大部分问题但极端场景下我建议用SetThreadAffinityMask把计时线程绑在一个核心上确保读数稳定。1.3 C11 带来的chrono库跨平台的现代答案C11引入的chrono库是目前的通用解。它提供了三种时钟std::chrono::system_clock系统实时时钟对应time()的精度升级版。它会受到用户修改系统时间、NTP校时的影响可能向前或向后跳变。不适合测耗时。std::chrono::steady_clock单调时钟只增不减不受系统时间调整影响专门为测量时间间隔设计。跨平台性能测试的首选。std::chrono::high_resolution_clock号称最高精度的时钟但老标准里没有规定它到底是steady还是system各编译器实现差异极大。建议直接当它不存在需要什么语义就显式使用对应时钟。#include chrono #include cstdio int main() { auto start std::chrono::steady_clock::now(); // 被测代码... auto end std::chrono::steady_clock::now(); auto diff end - start; double ms std::chrono::durationdouble, std::milli(diff).count(); std::printf(steady_clock elapsed: %.3f ms\n, ms); return 0; }用steady_clock作为统一的计时方案代码写一遍Windows/Linux/macOS都能跑。后面我提到的所有测试代码也默认基于它。1.4 各计时接口横向对比接口平台精度量级时钟类型适用场景clock()跨平台平台相关通常1msLinux下CPU时间/Windows下墙钟时间粗略估计纯CPU耗时跨平台对比不推荐GetTickCount()Windows10~16ms墙钟秒级以上的粗粒度耗时QueryPerformanceCounter()Windows亚微秒墙钟Windows下高精度计时std::chrono::steady_clock跨平台平台相关Linux通常ns单调时钟跨平台高精度计时首选std::chrono::system_clock跨平台平台相关墙钟可变获取当前时间点不适合测耗时我的建议很简单新代码直接用steady_clock不要用high_resolution_clock。只有当你明确只需要Linux或只需要Windows——并且对性能有极致要求——再考虑平台特定的QPC或clock_gettime(CLOCK_MONOTONIC)。2. 精度与分辨率的真相为什么你测出来的时间总是“不准”很多人在Stack Overflow上提过类似问题“我用了steady_clock实测一个函数耗时为什么每次运行结果都不一样有时候甚至是0”这不是steady_clock坏了而是你忽略了分辨率、精度和耗时本身量级之间的关系。2.1 分辨率不等于精度更不等于真实耗时的下界分辨率是计时器两次读数之间能区分的最小间隔。Linux下steady_clock通常基于CLOCK_MONOTONIC在现代内核中分辨率可到纳秒级。但分辨率高不代表你测一个小函数能测准因为函数本身可能只花了几十纳秒而一次now()调用的开销就要几十纳秒你测出的“耗时”可能一大半是计时器自己花的。所谓“测出了0ms”通常有两种情况函数耗时确实小于计时器分辨率比如一次内联的整数加法这时两次now()读数相同间隔为0。编译器把被测代码整个优化掉了后面会细说。对付这种超短耗时业界标准做法是“重复运行多次总耗时除以次数”而不是测一次就收工。这也叫 benchmarking 中的批量计时法。#include chrono #include cstdio #include cstdint volatile uint64_t sink 0; void fast_func() { sink (sink * 131) 7; } int main() { const int reps 1000000; auto start std::chrono::steady_clock::now(); for (int i 0; i reps; i) { fast_func(); } auto end std::chrono::steady_clock::now(); double avg_ns std::chrono::durationdouble, std::nano(end - start).count() / reps; std::printf(average: %.2f ns/op\n, avg_ns); return 0; }这个例子里的volatile很关键。sink被声明为volatile uint64_t告诉编译器“这个变量可能被外部修改你别乱优化掉”。因为fast_func的写入结果没人读取不这么处理-O2下编译器会把整个循环优化成空操作测出来的时间接近零——这是初学者最容易踩的坑。2.2 编译器优化对计时结果的三重干扰编译器优化是计时不准的头号元凶它的干扰方式至少有三层第一层优化掉无用代码。如前面所说你的被测函数如果计算结果没有对外产生副作用编译器有权把它整个删除。解决方法是把结果写入一个volatile变量或者传给一个外部可见的函数比如用printf打印但打印本身也耗时需要权衡。第二层循环展开和指令重排。-O2下编译器可能把循环展开成多条流水线指令改变指令执行的顺序和密度也可能把被测函数内的代码提升到now()调用之前执行使得计时区间不真实。对付指令重排没有万全之策只能用一些内存栅栏memory barrier去约束它。第三层链接时优化LTO。如果开启了-flto编译器可以在整个程序范围内做优化跨编译单元把你的被测函数进行内联或常量传播这会让单测结果严重失真。做性能测试时建议被测部分用一个单独的编译单元或者禁用LTO。Google的Benchmark库提供了一个DoNotOptimize工具函数本质就是用一个内联汇编空壳让编译器无法消除被测代码的结果。这是一个非常实用的小技巧template typename T inline void do_not_optimize(T const value) { asm volatile( : : r,m(value) : memory); }原理不复杂空的内联汇编块没有任何实际指令但告诉编译器“这段汇编依赖value并且可能读写内存”于是编译器必须保留与value相关的计算又不会真正插入代码影响测量。在自己写benchmark时给被测对象加一层这玩意儿能省掉很多困惑。2.3 操作系统的干扰进程调度、频率缩放、超线程即使代码写得再干净操作系统层面的噪声也躲不掉。Linux下你的进程随时可能被切换出CPU等一会儿再回来这段时间不管你用steady_clock还是QPC都会被计算在内导致单次测量出现异常尖峰。CPU频率缩放DVFS是另一个大头。现在的CPU普遍支持睿频和省电降频程序刚启动时频率较高跑一会儿可能因为温度或功耗限制降频。所以同一个算法你用“先跑一遍热身”的方式测出的时间和冷启动直接测能差20%以上。超线程的影响更适合用来说明多核环境下的干扰两个线程跑在不同逻辑核心上共享同一个物理核的执行单元一个线程的重负载会明显拖慢另一个线程的程序——就算它们属于完全不同进程。对这些问题我的建议是进行测试前先跑一段不记时的热身代码把频率和缓存状态拉起来。同一测试跑多次取中位数而不是平均值因为平均值容易受极端尖峰影响。Linux下可用taskset绑定CPU核心减少调度迁移带来的噪声。taskset -c 0 ./my_benchmark把进程固定在核心0上能有效降低操作系统调度导致的时间抖动。这个操作对性能测试来说简单又有效。2.4 一个测量方案的可信度怎么评估判断一个计时方案靠不靠谱不能只看单次结果。整体来说我评估一个测试方案可用性的标准有三个重复性同样的测试重复10次结果应该落在一个较窄的区间内比如±5%。如果波动超过20%说明有外界噪声测试条件没控制好。线性性把被测数据规模放大一倍耗时应该接近翻倍对复杂度O(n)的算法而言。如果不符合预期先怀疑计时是否覆盖了正确的代码段。逆向验证用空循环做对照测试框架本身的overhead要远小于被测代码的耗时。如果测试条件有限没有条件绑核、关睿频那至少要保证对比测试在同一个环境下执行并且交替运行A-B-A-B以减少时间顺序带来的偏差。3. 多线程程序的计时测“总时间”还是测“每线程时间”多线程程序的计时是一个更麻烦的领域。这里的核心问题是你到底想测什么3.1steady_clock测整段程序没问题如果你的目标是知道“从开始到全部线程结束用户等了多久”那么用steady_clock包住整个std::thread创建、join的区间就行。这是墙钟时间准确反映用户体感。这种场景没有歧义。但如果你研究的是单个线程里某段代码的真正耗时——比如某个工作线程处理一批任务的耗时——麻烦就来了。其他线程的竞争、锁等待、缓存竞争都会计入这段墙钟时间导致测出的耗时比理论计算时间大很多而且每次都不一样。3.2 多线程环境下的“自旋代替sleep”问题一个常见的陷阱是用std::this_thread::sleep_for去模拟工作负载再用steady_clock测整段耗时。你的本意可能是测试线程调度效率但sleep会让出CPU测出来的结果更像操作系统的调度延迟而不是你代码的耗时。很多“我的多线程程序性能怎么这么差”的问题根源其实是测量方案里混入了sleep。在性能测试里模拟计算密集工作负载的正确姿势是忙等或真实计算void busy_work(int iterations) { volatile uint64_t x 0; for (int i 0; i iterations; i) { x i; } }这样线程会真实占用CPU测量结果才能反映多线程竞争的实际情况。3.3 每线程时间戳与日志系统的时序还原如果你确实需要还原每个线程内部的时间线比如分析某个多阶段流水线里每阶段的耗时建议的做法是在线程内部记录带时间戳的事件日志最后统一排序分析。而时间戳必须来自clock需要考虑时钟是否单调。多线程还有一个经典陷阱一个线程记录了“开始时间”另一个线程记录了“结束时间”中间通过某个共享变量同步。如果这个同步操作没有正确的内存序memory order那么记录的时间可能根本对应不上。应该保证每个线程都记录自己两次操作之间的事件点外部再用统一的时钟对齐。3.4 实测案例共享变量竞争导致的计时失真我用一个例子说明多线程计时有多容易被误读。假设有一个多线程累加程序每个线程把共享计数器累加N次用steady_clock包住整个线程创建执行join的过程测出总耗时。如果把计数器改为用std::atomicint并提供memory_order_relaxed你会发现耗时下降非常明显。但如果你测的是每个工作线程内部单次累加的耗时由于缓存行竞争不同核心上的耗时差异极大。这种情况下只输出一个“总耗时”数字会掩盖很多信息。更好的做法是记录每个线程的单独耗时给出分布数据最小/最大/平均而不是只给一个总和。4. 算法性能测试与比赛场景手写计时器的实用模板在算法学习、竞赛和数据结构课程设计中计时通常用于对比不同算法比如快速排序和归并排序在同样数据规模下的表现。这一节我给出几个可以直接使用的模板和实测经验。4.1 通用跨平台计时模板先把最基础的跨平台模板写出来兼容C11及以上标准#include chrono #include cstdio #include vector #include algorithm template typename Func double time_it_ms(Func func, int repeat 1) { auto start std::chrono::steady_clock::now(); for (int i 0; i repeat; i) { func(); } auto end std::chrono::steady_clock::now(); return std::chrono::durationdouble, std::milli(end - start).count() / repeat; } int main() { std::vectorint data(100000); // 填充随机数据... std::srand(42); std::generate(data.begin(), data.end(), std::rand); double sort_ms time_it_ms([] { std::vectorint copy data; // 拷贝一份保证每次排序输入相同 std::sort(copy.begin(), copy.end()); }, 10); std::printf(std::sort average: %.3f ms\n, sort_ms); return 0; }这里面有个容易忽略的点每次排序前要拷贝一份数据否则第一次排序后data已经有序后面的测试相当于对有序数组排序结果会严重偏向快速排序这类对有序输入友好的算法。保证每次被测试的输入状态一致是算法对比的底线。4.2 测试时要避免的常见错误把输入生成也计入耗时如果你在计时区间内同时做了数据生成和排序得分高不代表排序快。把数据准备放在计时外面。只看一次结果单次运行受系统调度影响太大至少跑5轮取中位数。Debug模式下测性能Ddebug编译基本不优化STL容器和算法性能大打折扣。做性能对比要用Release模式加上-O2。重复测试时输入未重置如4.1中的例子排序会修改原数组必须拷贝。4.3 竞赛中常用RDTSC吗在算法竞赛圈偶尔有人提到RDTSC指令Read Time-Stamp Counter它直接读取CPU的时间戳计数器理论精度可达纳秒级。#include cstdint static inline uint64_t rdtsc() { unsigned int lo, hi; __asm__ volatile (rdtsc : a(lo), d(hi)); return ((uint64_t)hi 32) | lo; }但我不推荐在竞赛或普通开发里使用。原因有三RDTSC在现代CPU上受乱序执行影响指令不一定在你希望的时机执行需要配合cpuid或mfence保证串行化代码复杂度上升。计数器的频率不是固定Hz和CPU当前频率相关虽然现代CPU大都有恒定TSC但在老平台上不保证。跨平台可移植性差ARM架构是另一套指令。在竞赛场景clock()配合CLOCKS_PER_SEC做粗略计时或者直接用chrono已经足够。分析算法复杂度用理论复杂度就够了不需要微秒级实测。4.4 一个完整的数据结构课设计时案例拿热搜里的“数据结构课程设计C/C版——植物百科数据的管理与分析”为例这种项目通常需要对比几种查找算法顺序查找、折半查找、二叉搜索树查找的性能。完整的计时方案应该是读入植物百科数据全部加载到内存这部分不计入单次查找耗时。准备查询样本从数据中随机抽取若干关键词固定查询顺序。对每种查找结构用同一组查询样本执行N次统计总耗时除以查询次数得到平均单次耗时。对照组与实验组交替执行每组跑5轮取中位数。加上前面的模板得到一个可与别人重复性对比的结论。关键是把“准备数据”“执行查找”“汇总耗时”三段严格分离用固定查询集合保证公平。5. VSCode环境下的计时实操从配置到智能提示的闭环热搜里VSCode相关词出现频率很高VSCode确实是目前写C/C的主流轻量IDE。我针对“VSCode配置C/C环境”和“C/C结构体成员补全错误”这两个话题讲一下实际用VSCode做性能测试时需要注意的几个点。5.1 按对编译器MinGW-w64、MSVC与LLVM的选择VSCode只是一个编辑器真正干活的编译器取决于你安装的工具链。常见选择有Windows下使用MinGW-w64g配置简单与Linux的GCC行为一致适合算法和跨平台项目。Windows下使用MSVCVisual Studio Build Tools性能分析工具更好但命令行使用不如MinGW直观。macOS/Linux下直接使用系统自带的clang或g。写计时相关代码时MinGW-w64的steady_clock实现基于QueryPerformanceCounter精度很高用它和Linux上的GCC对比量级基本一致我做跨平台测试时主要用它。5.2 tasks.json与launch.json的合理配置用VSCode编译并运行计时程序核心是配置两个JSON文件。tasks负责编译生成可执行文件{ version: 2.0.0, tasks: [ { label: C/C: g build, type: cppbuild, command: g, args: [ -fdiagnostics-coloralways, -g, -O2, -stdc17, ${fileDirname}/*.cpp, -o, ${fileDirname}/output.exe ], group: build, problemMatcher: [$gcc] } ] }这里关键参数是-O2。如果做性能测试必须开启优化否则测的是编译器的“摆烂”速度。-g用来生成调试信息不影响性能保留可以方便排查段错误。launch.json负责运行调试。运行纯计时程序其实不需要debugger直接F1执行“Run Build Task”然后在终端运行exe即可。默认的launch配置里如果使用了externalConsole: true程序会另起窗口计时结果会被窗口关闭吞掉改成false更顺手。5.3 计时输出里夹带Debug日志的风险很多人习惯在计时代码里加std::cout start或printf打点以便确认运行到哪一步了。这在性能测试里是大忌——控制台输出走I/O耗时动不动就是毫秒级比你算法本身的耗时还大。正确方案是计时区间内不做任何I/O。需要确认位置的话用volatile标志位或者最后统一输出。比如auto start std::chrono::steady_clock::now(); int result some_algorithm(data); // 不打印 auto end std::chrono::steady_clock::now(); printf(result%d, elapsed%.3f ms\n, result, get_ms(start, end));先把结果存到变量里计时结束后再打印。这样既能看到结果确认算法没跑错又不污染计时。5.4 结构体成员补全错误的排查思路在热词里有一个“VSCode C/C结构体成员补全错误”这个话题和计时本身关系不大但非常影响写代码的效率。如果结构体成员提示不出来或者提示错乱最常见的三个原因缺少#include对应的头文件IntelliSense没法解析结构体定义。intellisense模式与编译器不匹配比如实际用MinGW但C/C扩展选了msvc-x64模式导致解析错误。解决办法是在c_cpp_properties.json里将intelliSenseMode改为gcc-x64并正确设置compilerPath。使用了宏或模板导致语义分析失败遇到这种情况先尝试简化代码定位到具体触发补全错误的最小示例。和计时组合起来看的话我的建议是先确保编辑器智能提示正常、编译命令正确再开始性能测试。否则很容易出现代码能跑但计时程序因为个别文件没被编译进去而数据缺失的尴尬情况。6. 时间测量之外的坑编译选项、CPU频率与Benchmark基础设施做时间测量如果只关注代码本身很容易漏掉外围基础设施的影响。这一节把和“测量动作”紧密相关的一些容易忽视的点集中说一下。6.1-O0还是-O2编译选项如何决定测量意义先给结论性能测试用-O2或Release模式调试和定位逻辑错误用-O0。很多初学者在VSCode或者IDE里默认跑的是Debug模式没有开优化然后拿着这个数据去对比不同算法的优劣得出的结论基本没有参考价值。用-O2跑出来的时间才更接近实际部署状态。有两类特殊情况需要单独说明如果你的代码里有未定义行为比如有符号整数溢出后继续使用或者越界读写-O2可能让程序行为和预期完全不一样。遇到“我开了优化就出错”的情况优先检查代码而不是怀疑编译器。有些极其热点的函数在-O2下可能被内联到调用方导致你测的“函数耗时”其实包含了调用方的循环开销。想精确测一个函数可以在被测函数前加__attribute__((noinline))GCC/Clang或__declspec(noinline)MSVC禁止内联。6.2 CPU频率与节能策略对测试结果的干扰现代CPU的睿频和节能策略对计时结果的影响非常大。举个例子一段执行时间100ms的计算任务如果CPU运行在800MHz的低频状态和4GHz睿频状态耗时可以差4倍。而Linux的CPU调控器governor默认可能是powersave或ondemandWindows的电源计划也分“节能”“均衡”“高性能”。如果对测量精度要求高在Linux下可以临时把调控器改成performancesudo cpupower frequency-set -g performance或者使用turbo控制工具限制睿频。Windows下则把电源计划切到“高性能”。当然如果你是做面向普通用户的体验测试反而应该用默认配置因为这更接近真实用户环境。关键是你得知道自己测的是“极限性能”还是“典型体验”并且把前提写明。6.3 缓存、预热与中位数如何让对比数据有统计意义即使解决了编译器优化和CPU频率问题还有缓存的干扰。同一个函数第一次调用时指令和数据要从内存加载到缓存耗时可能比后续调用高一个数量级。现代benchmark框架的基本操作是先“预热”warm-up把缓存和分支预测器“加热”到稳定状态再开始正式计时。预热之外统计口径也很重要。我不推荐用平均值作为唯一输出指标因为一次系统调度抖动就可能让平均值严重偏高。推荐做法是跑N轮输出中位数或直方图分布。用std::nth_element取中位数写起来很麻烦其实直接排序后取中间值就行std::vectordouble samples; for (int i0; i5; i) { // 每轮先预热再计时 samples.push_back(run_one_round()); } std::sort(samples.begin(), samples.end()); double median_ms samples[samples.size() / 2];如果多个方案的对比差距小于噪声幅度比如方案A中位数10ms方案B中位数10.2ms我通常直接判定“没有显著差异”不为0.2ms去调代码。真正要关注的是数量级级别的优化收益。6.4 一个简单的多轮计时模板把上面所有讨论落成一个实用模板直接可用于对比两个函数#include chrono #include cstdio #include algorithm #include vector template typename Func double bench(Func func, int rounds 7, int warmup 2) { for (int i 0; i warmup; i) { func(); } std::vectordouble times; for (int i 0; i rounds; i) { auto t0 std::chrono::steady_clock::now(); func(); auto t1 std::chrono::steady_clock::now(); times.push_back(std::chrono::durationdouble, std::milli(t1 - t0).count()); } std::sort(times.begin(), times.end()); return times[rounds / 2]; }使用方式int main() { auto data create_test_data(); double med_a bench([] { algorithm_a(data); }); double med_b bench([] { algorithm_b(data); }); printf(A median: %.3f ms\nB median: %.3f ms\n, med_a, med_b); }这里create_test_data必须返回一个可以被重复调用的副本机制或者algorithm_a和algorithm_b内部自己拷贝数据保证每轮输入一致。7. 我踩过的几个高价值坑教训汇总最后分享几个我在实际做C/C计时性能测试时踩过的坑每一个都花了不少时间才排查明白。第一个坑是把clock()当墙钟用。当时写一个网络程序的性能测试服务端接收请求、处理、回复我用clock()统计单次请求耗时结果数值小得离谱。排查半天才意识到clock()在Linux下只统计CPU时间而程序大部分时间在等待socket事件CPU时间可能只有真实耗时的十分之一。从那以后凡是有I/O的场景我一律用steady_clock。第二个坑是被测函数被整体优化掉。我用-O2测试一个简单的哈希函数测出来0.000001ms比空函数还快。后来才发现哈希结果没被使用编译器把整个函数优化成了常数计算循环体直接删除。给结果加上do_not_optimize或volatile输出之后数据立刻正常了。第三个坑是用high_resolution_clock跨平台对比数据。在某Linux版本上它是steady_clock的别名而某个旧版Windows工具链上它可能退化为system_clock受校时影响会出现负数间隔。排查方式是在代码里用is_steady()打日志所有平台统一换成steady_clock后问题消失。第四个坑是多线程计时时忘了操作系统噪声。我测试一个并行排序算法某次运行结果比平均值慢两倍一开始以为代码有bug后来看系统监控日志发现那段时间有一个后台杀毒扫描进程在吃CPU。从那以后我在做性能对比前总是先检查系统负载并且用多轮中位数来削弱偶发噪声。还有一个小技巧是计时程序尽量用专用小工程别放在庞大的项目里编译。项目里宏定义、头文件包含、预编译头文件都会干扰测试代码的实际行为。单独一个几KB的.cpp文件依赖最少可复现性最高。这个习惯让我排查问题时的变量少了很多。如果你正在做跨平台库的性能测试建议从第一天就把Google Benchmark或类似库集成进来它能自动处理预热、重复、统计和优化屏障比自己手写计时器靠谱得多。不过就算不用框架理解了本章讲的这些原理手写一个5行的计时工具也已经够应对绝大多数场景。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑