编写 C++ 程序或优化算法时,我们经常需要测量某一段代码到底运行了多少毫秒、多少微秒,以此对比不同写法的快慢,或者排查程序卡顿的原因。
测量代码耗时的思路和体育课上用秒表测百米跑完全一样:在代码开始执行前按一下秒表记下起点,在代码执行结束后再按一下秒表记下终点,最后把两个时刻相减即可。在 C++ 中,这只 “秒表” 就是 <chrono> 头文件提供的单调稳定时钟 steady_clock,整体计时流程如下图所示。

测量代码耗时的基本步骤
为什么测量耗时要选用 steady_clock 而不是 system_clock 呢?这是因为 system_clock 对应系统的日历时间,如果在程序运行期间电脑自动联网校时,算出来的时间差就会失准甚至变成负数;而 steady_clock 只会匀速往前走,专门用来测量时间间隔。
使用 steady_clock 测量一段代码的运行时间,固定分为以下 3 步:
- 记录起始时间点:在待测代码开始前,调用
steady_clock::now()记录起始时间点start。 - 执行待测代码:运行需要测量的循环、算法或函数调用。
- 记录结束时间点并求差:在待测代码结束后,再次调用
steady_clock::now()记录结束时间点end,并通过end - start算出时间差。
语法:
auto start = std::chrono::steady_clock::now();
// 待测量的代码
auto end = std::chrono::steady_clock::now();
auto cost = std::chrono::duration_cast<目标单位>(end - start);说明:
把 end - start 传给 duration_cast 后,我们可以根据代码的运行快慢选择合适的时间单位,最后调用 cost.count() 就能取出耗时的整数值。不同耗时量级常用的时间单位如下表所示。
| 时间单位类型 | 输出缩写 | 适用测量场景 |
|---|---|---|
| std::chrono::seconds | s(秒) | 耗时较长的大批量文件读写、网络下载或大型任务 |
| std::chrono::milliseconds | ms(毫秒) | 日常业务函数、常规循环、接口响应耗时(最常用) |
| std::chrono::microseconds | us(微秒) | 单次内存查找、短小算法函数(耗时在几毫秒以内) |
| std::chrono::nanoseconds | ns(纳秒) | 极短的底层指令或单次简单运算 |
示例 1:测量循环累加的耗时
#include <chrono>
#include <iostream>
int main() {
// 记录起始时间点
auto start = std::chrono::steady_clock::now();
// 循环累加五千万次
long long sum = 0;
for (int i = 1; i <= 50000000; i++) {
sum += i;
}
// 记录结束时间点并换算为毫秒
auto end = std::chrono::steady_clock::now();
auto diff = end - start;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
std::cout << "计算结果 sum = " << sum << '\n';
std::cout << "耗时:" << ms.count() << " ms\n";
return 0;
}运行结果如下。
计算结果 sum = 1250000025000000
耗时:95 ms分析:
在这个例子中,我们在 for 循环前后分别调用 steady_clock::now() 记录了起点 start 与终点 end。紧接着 end - start 算出的就是五千万次循环累加消耗的时间,再通过 duration_cast 换算为整数毫秒 ms。具体的耗时数值取决于当前电脑的 CPU 速度与编译选项,每次运行会有轻微波动。
另外,初学的小伙伴在放置 start 和 end 时要注意:只包住真正需要测量的计算代码即可,千万不要把等待用户按键的 std::cin 输入语句也包进计时区间里,否则测出来的大部分时间都是在等待键盘输入。
// 推荐
std::cin >> n;
auto start = std::chrono::steady_clock::now();
// 执行待测量的计算逻辑
auto end = std::chrono::steady_clock::now();
// 不推荐
auto badStart = std::chrono::steady_clock::now();
std::cin >> n;
// 执行待测量的计算逻辑
auto badEnd = std::chrono::steady_clock::now();保留小数精度的耗时统计
在示例 1 中,我们使用了标准库预定义的整数毫秒 milliseconds。但如果待测代码执行得非常快,比如总耗时只有 0.4 毫秒(即 400 微秒),直接通过 duration_cast<milliseconds> 转换就会截断成 0 ms。
如果我们既想以 “毫秒” 或 “秒” 为单位查看结果,又不想丢失小数点后面的精度(比如希望打印出 1.850 ms 或 0.002 s),可以使用浮点型时间段 std::chrono::duration<double>。
语法:
std::chrono::duration<double> costSec = end - start;
std::chrono::duration<double, std::milli> costMs = end - start;说明:
在 std::chrono::duration<double, 单位比例> 中,第 1 个模板参数指定为 double,表示使用双精度浮点数保存时长。因为 double 本身就能保留小数,不会像整数单位那样丢失精度,所以我们不需要再套一层 duration_cast,直接使用 = 接收 end - start 即可。
第 2 个模板参数用于指定单位比例:省略不写时默认以 “秒” 为单位;传入 std::milli 则表示以 “毫秒”(千分之一秒)为单位。
示例 2:统计微秒与小数毫秒耗时
#include <chrono>
#include <iomanip>
#include <iostream>
int main() {
using namespace std::chrono;
auto start = steady_clock::now();
// 循环累加一百万次
long long total = 0;
for (int i = 1; i <= 1000000; i++) {
total += i;
}
auto end = steady_clock::now();
// 换算为整数微秒
auto costUs = duration_cast<microseconds>(end - start);
// 换算为带小数的毫秒和秒
duration<double, std::milli> costMs = end - start;
duration<double> costSec = end - start;
std::cout << "计算结果 total = " << total << '\n';
std::cout << "整数微秒:" << costUs.count() << " us\n";
std::cout << std::fixed << std::setprecision(3);
std::cout << "小数毫秒:" << costMs.count() << " ms\n";
std::cout << "小数秒数:" << costSec.count() << " s\n";
return 0;
}运行结果如下。
计算结果 total = 500000500000
整数微秒:1850 us
小数毫秒:1.850 ms
小数秒数:0.002 s分析:
从运行结果可以看出,一百万次循环的耗时约为 1850 微秒,也就是 1.850 毫秒。如果只使用整数毫秒 milliseconds,小数点后面的 .850 就会直接截断,结果变成 1 ms。
通过 duration<double, std::milli> 把耗时保存为浮点数,再配合前面学习输出格式化时用过的 std::fixed 和 std::setprecision(3),我们就能清晰地打印出 3 位小数的耗时了。
如果小伙伴们觉得双模板参数的写法不太好记,也可以先算出整数微秒 costUs,再除以 1000.0 换算成小数毫秒。对于求小数毫秒来说,下面 2 种方式是等价的。
// 方式 1
duration<double, std::milli> costMs = end - start;
double ms = costMs.count();
// 方式 2
auto costUs = duration_cast<microseconds>(end - start);
double ms = costUs.count() / 1000.0;提示 如果待测循环只做计算却不使用结果,开启 -O2 等编译优化后,编译器可能会把它当成无效代码直接删掉,导致测出 0 ms。因此对比两个算法谁更快时,大家记得把最终的计算结果打印出来。
