C++计算程序运行时间

zxl19 2021-06-02

几种C++计算程序运行时间的方法,包括针对代码段的高、低精度计时方法以及对于程序整体的计时方法。

代码段运行时间

高精度

  1. C++11引入chrono库,用于精确计算程序运行时间,使用时需要包含头文件:

     #include <chrono>
    
  2. chrono库定义的类、函数、常量等均在chrono子命名空间中,为了使表示形式简便,本文默认使用std::chrono命名空间:

     using namespace std::chrono;
    
  3. chrono库定义了三个重要概念:

    • 持续时间(durations):提供duration模板类将周期计数和周期精度耦合,用于测量时间跨度;
    • 时间点(time points):提供time_point模板类表示相对于时钟纪元(epoch)的持续时间,用于表示时刻;
    • 时钟(clocks):提供system_clocksteady_clockhigh_resolution_clock模板类表示不同计时特性的时钟,用于在时间点与实际物理时间之间建立联系;

持续时间

原型声明:

template <class Representation,     // 算数类型,用于表示周期计数
          class Period = ratio<1>   // 分数表示的有理数,用于表示周期精度,默认为1秒
          >
class duration;

常用成员函数原型声明:

constexpr rep count() const;        // 返回周期计数
static constexpr duration zero();   // 返回为零的持续时间
static constexpr duration min();    // 返回持续时间能够表示的最小值
static constexpr duration max();    // 返回持续时间能够表示的最大值

chrono库对于实例化的duration模板类定义了别名,用于表示常用的时间类型:

持续时间类型 Representation Period
hours 至少23位有符号整型 std::ratio<3600, 1>
minutes 至少29位有符号整型 std::ratio<60, 1>
seconds 至少35位有符号整型 std::ratio<1, 1>
milliseconds 至少45位有符号整型 std::ratio<1, 1000>
microseconds 至少55位有符号整型 std::ratio<1, 1000000>
nanoseconds 至少64位有符号整型 std::ratio<1, 1000000000>

不同持续时间类型之间可以使用duration_cast模板函数相互转换,原型声明:

template <class ToDuration, class Rep, class Period>
constexpr ToDuration duration_cast(const duration<Rep, Period>& dtn);

时间点

原型声明:

template <class Clock, class Duration = typename Clock::duration>
class time_point;

常用成员函数原型声明:

duration time_since_epoch() const;  // 返回相对于时钟纪元的持续时间
static constexpr time_point min();  // 返回时间点能够表示的最小值
static constexpr time_point max();  // 返回时间点能够表示的最大值

时钟

chrono库提供了三种时钟类型:

  1. system_clock:系统时钟;

    • 实时(realtime),表示真实时间,可以转换为日历时间,日历时间是一个整数,表示协调世界时(Universal Coordinated Time,UTC)1970年1月1日00:00起经过的秒数,Unix时间就采用了这种表示方式;
    • 有符号(signed count),可以用负值表示新纪元时间(epoch time)之前的时间,新纪元时间指的是即1970年1月1日00:00 UTC;
    • 系统范围(system-wide),系统中运行的所有进程可以获得相同的时间;
  2. steady_clock:稳定时钟,专门用于计算时间间隔;

    • 单调性(monotonic),返回的时间是单调递增的,每次调用now()成员函数返回的时间总是比之前返回的时间大;
    • 稳定性(steady):每次时钟走时对应的实际时间是相同的;
  3. high_resolution_clock:高精度时钟;

    • 最高精度(highest precision),是精度最高的时钟类型,时钟走时周期最小;
    • 在不同的标准库中实现不一致,通常是system_clocksteady_clock的别名,具体实现取决于标准库和系统配置,因此不建议使用;

常用成员函数原型声明:

static time_point now() noexcept;
static time_t to_time_t (const time_point& tp) noexcept;
static time_point from_time_t (time_t t) noexcept;

计算程序耗时

steady_clock为例:

#include <chrono>

steady_clock::time_point start = steady_clock::now();   // 开始时间
// do something
steady_clock::time_point end = steady_clock::now();     // 结束时间
milliseconds duration =
    duration_cast<milliseconds>(end - start);           // 计算开始时间和结束时间之间用milliseconds表示的持续时间
double period_count = duration.count();                 // 计算用milliseconds表示的持续时间中有多少个周期
std::cout << "Spent " << period_count * milliseconds::period::num / milliseconds::period::den << " seconds."
          << std::endl;                                 // 使用周期数量和每个周期对应的时间计算运行时间(单位:s)
std::cout << "Spent " << period_count << " milliseconds." << std::endl; // 使用周期数量直接输出时间(单位:ms)

double system_stamp = duration<double>{steady_clock::now().time_since_epoch()}.count();

具体工程实现参考gaoxiang12/faster-lio

低精度

  1. ctime库用于获取和操作与日期和时间相关的信息,使用时需要包含头文件:

     #include <ctime>
    
  2. ctime库定义了三个表示时间的数据类型:

    • clock_t:表示时钟滴答(clock tick)计数的基础数据类型;
    • time_t:表示日历时间(calendar time)的基础数据类型;
    • tm:表示日历时间的结构体类型:

      数据成员 类型 含义 范围 备注
      tm_sec int 0-60 60表示闰秒
      tm_min int 0-59  
      tm_hour int 0-23  
      tm_mday int 1-31  
      tm_mon int 0-11  
      tm_year int   从1900年开始
      tm_wday int 星期 0-6  
      tm_yday int 天数 0-365  
      tm_isdst int 是否使用夏令时   正值为是,负值为否

计算程序耗时

#include <ctime>

clock_t start = clock();    // 开始时间
// do something
clock_t end = clock();      // 结束时间
std::cout << "Spent " << double(end - start) / CLOCKS_PER_SEC   // 宏定义,表示每秒中的时钟滴答数
          << " seconds." << std::endl;                          // 输出时间(单位:s)

格式化时间戳

常用成员函数原型声明:

char* asctime(const struct tm* timeptr);    // 将tm类型转换为字符串,格式为“星期 月 日 时:分:秒 年”
char* ctime(const time_t* timer);           // 将time_t类型转换为字符串,相当于asctime(localtime(timer))
struct tm* gmtime(const time_t* timer);     // 将time_t类型转换为tm类型的UTC时间
struct tm* localtime(const time_t* timer);  // 将time_t类型转换为tm类型的本地时间
size_t strftime(char* ptr, size_t maxsize, const char* format,
                const struct tm* timeptr);  // 将tm类型转换为字符串,支持自定义格式

与时间相关的格式化输出符号:

格式化输出符号 含义 备注 范围 示例
%a 星期缩写     Thu
%A 星期全称     Thursday
%b 月份缩写     Aug
%B 月份全称     August
%c 日期和时间表示 格式为“星期 月 日 时:分:秒 年”   Thu Aug 23 14:55:02 2001
%C 年份除以100并截断为整数   00-99 20
%d 月中的某一天 两位,不足两位前面补零 01-31 23
%D 短日期 格式为“月/日/年”,相当于%m/%d/%y   08/23/01
%e 月中的某一天 两位,不足两位前面补空格 1-31 23
%F 短日期 格式为“年-月-日”,相当于%Y-%m-%d   2001-08-23
%g 基于周的年份(week-based year)后两位 基于周的年份从最接近1月1日的第一个星期一开始 00-99 01
%G 基于周的年份     2001
%h 月份缩写 相当于%b   Aug
%H 24小时制的小时数 两位,不足两位前面补零 00-23 14
%I 12小时制的小时数 两位,不足两位前面补零 01-12 02
%j 年中的某一天 三位,不足三位前面补零 001-366 235
%m 月份 两位,不足两位前面补零 01-12 08
%M 分钟数 两位,不足两位前面补零 00-59 55
%n 换行符 相当于\n    
%p 上午或下午 使用AM或PM表示   PM
%r 12小时制表示的时间 格式为“时:分:秒 上午或下午”   02:55:02 pm
%R 24小时制表示的时间 格式为“时:分”,相当于%H:%M   14:55
%S 秒数 两位,不足两位前面补零 00-61 02
%t 水平制表符 相当于\t    
%T ISO 8601标准规定的时间 格式为“时:分:秒”,相当于%H:%M:%S   14:55:02
%u ISO 8601标准规定的星期 星期一为1,之后依次递增 1-7 4
%U 年中的某一周 以第1个星期日为第1周的第1天 00-53 33
%V ISO 8601标准规定的周   01-53 33
%w 星期 星期日为0,之后依次递增 0-6 4
%W 年中的某一周 以第1个星期一为第1周的第1天 00-53 34
%x 日期表示 格式为“月/日/年”   08/23/01
%X 时间表示 格式为“时:分:秒”   14:55:02
%y 年份后两位   00-99 01
%Y 年份     2001
%z ISO 8601标准规定的当前时区相对于UTC的偏移量 1分钟为1,1小时为100,如果无法确定时区,则输出空字符串   +100
%Z 时区名称或缩写 如果无法确定时区,则输出空字符串   CDT
%% 百分号     %

示例:

#include <ctime>
#include <iomanip>  // std::put_time()
#include <sstream>  // std::ostringstream

std::string ConvertUnixStampToString(double unix_stamp) {
  // 将double类型的时间戳转换为time_t类型
  std::time_t time = static_cast<std::time_t>(unix_stamp);

  // 将time_t类型的时间戳转换为tm类型的本地时间
  std::tm local_time = *std::localtime(&time);

  // 将tm类型的本地时间格式化为字符串,格式为“年-月-日 时:分:秒”
  std::ostringstream oss;
  oss << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S");

  // 返回字符串
  return oss.str();
}

程序整体运行时间

在Linux系统中使用time命令计算可执行文件的运行时间:

time ./a.out

参考

  1. cplusplus
  2. C++11——chrono库开发高精度计!我们可能学的不是同一门语言~-C语言编程俱乐部的文章-知乎
  3. C++11的chrono库-小武的文章-知乎
  4. chrono1-CSDN博客
  5. chrono2-CSDN博客
  6. C++下四种常用的程序运行时间的计时方法总结-阿贵的文章-知乎
  7. gaoxiang12/faster-lio
  8. ISO 8601 Date and time format-ISO
  9. Linux time命令-菜鸟教程