C++ 计算程序运行时间

编写 C++ 程序或优化算法时,我们经常需要测量某一段代码到底运行了多少毫秒、多少微秒,以此对比不同写法的快慢,或者排查程序卡顿的原因。

测量代码耗时的思路和体育课上用秒表测百米跑完全一样:在代码开始执行前按一下秒表记下起点,在代码执行结束后再按一下秒表记下终点,最后把两个时刻相减即可。在 C++ 中,这只 “秒表” 就是 <chrono> 头文件提供的单调稳定时钟 steady_clock,整体计时流程如下图所示。

用秒表测时理解 C++ 代码耗时测量流程

测量代码耗时的基本步骤

为什么测量耗时要选用 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。因此对比两个算法谁更快时,大家记得把最终的计算结果打印出来。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号