几种C++计算程序运行时间的方法,包括针对代码段的高、低精度计时方法以及对于程序整体的计时方法。
代码段运行时间
高精度
-
C++11引入chrono库,用于精确计算程序运行时间,使用时需要包含头文件:
#include <chrono>
-
chrono库定义的类、函数、常量等均在
chrono
子命名空间中,为了使表示形式简便,本文默认使用std::chrono
命名空间:using namespace std::chrono;
-
chrono库定义了三个重要概念:
- 持续时间(durations):提供
duration
模板类将周期计数和周期精度耦合,用于测量时间跨度; - 时间点(time points):提供
time_point
模板类表示相对于时钟纪元(epoch)的持续时间,用于表示时刻; - 时钟(clocks):提供
system_clock
、steady_clock
、high_resolution_clock
模板类表示不同计时特性的时钟,用于在时间点与实际物理时间之间建立联系;
- 持续时间(durations):提供
持续时间
原型声明:
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库提供了三种时钟类型:
-
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),系统中运行的所有进程可以获得相同的时间;
-
steady_clock
:稳定时钟,专门用于计算时间间隔;- 单调性(monotonic),返回的时间是单调递增的,每次调用
now()
成员函数返回的时间总是比之前返回的时间大; - 稳定性(steady):每次时钟走时对应的实际时间是相同的;
- 单调性(monotonic),返回的时间是单调递增的,每次调用
-
high_resolution_clock
:高精度时钟;- 最高精度(highest precision),是精度最高的时钟类型,时钟走时周期最小;
- 在不同的标准库中实现不一致,通常是
system_clock
或steady_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。
低精度
-
ctime库用于获取和操作与日期和时间相关的信息,使用时需要包含头文件:
#include <ctime>
-
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