使用C++ 11 chrono库处理时间

跨平台时间处理

对于时间处理的需求

  1. 获取当前时间,比如记录日志,精度一般到毫秒
  2. 计算时间间隔,比如判断代码块的运行时长
  3. 获取日期,日期换算
  4. 定时器

OS提供的API

  • 时间日期相关
time_t time() //返回从1970-01-01 00:00:00 +0000 (UTC)的秒数(C语言标准库提供)

int gettimeofday(struct timeval *tv, struct timezone *tz); //返回指定时区 回从1970-01-01 00:00:00 +0000 (UTC)的秒数及微秒

其中time()是跨平台的,现在已经在std标准中了,gettimeofday为linux系统特有,在windows平台并没有这个API。显然不同OS都会提供特有的处理时间的API。

  • 定时相关

一种跨平台的定时器,是通过select的timeout参数实现。

所以如果要实现跨平台,高精度时间及定时处理,是依赖于特定平台的

日期处理更多的是业务层代码中,时间的处理在底层,在这篇文章中是介绍是使用chrono时间处理API

C++ 11中时间和日期的处理

包括两种方式:

  1. C语言风格的方式,在time.h头文件包括一系列的API,在C++11中是被包含在ctime头文件中
  • time() 获取时间,用秒数表示
  • ctime() 传入一个 time_t类型,返回日期字符串
  • localtime 传入一个time_t类型,获取一个tm结构体,其中包括了年,月,日,时,分,秒

精度是到秒

  1. C++ 11引入的chrono库

精度高,最高精度是到1纳秒

C++ 11中的chrono

chrono的出现也正是为了解决跨平台时间处理这一繁琐的问题,它提供的功能覆盖了上面所提出的前三点时间处理需求,但遗憾的是它并没有包含定时器。

它引入了三个基本的概念:时间点,时间间隔,时间(日期)。时间处理的都是基于这三个概念。

看看C++ reference中的对三个概念的解释

Durations(时间间隔)
They measure time spans, like: one minute, two hours, or ten milliseconds.

Time points(时间点)
A reference to a specific point in time, like one’s birthday, today’s dawn, or when the next train passes.

Clocks(时间)
A framework that relates a time point to real physical time.时间点的通俗概念,包含了时间点,时间间隔,日期

Clock(时间/时钟)

Clock 跟time_t time()返回值的意义类似,代表这从时间纪元(1970-01-01 00:00:00 +0000),到现在的"距离",这个"距离"有不同的精度。

C++ 11中包含三种clock类型:system_clocksteady_clokhigh_resolution_clock,分别对应了不同的精度。并且每一种clock中都定义了特定的durationtime_point类型(因为精度不同)

system_clock::duration,system_clock::time_point
steady_clock::duration,steady_clock::time_point
high_resolution_clock::duration,high_resolution_clock::time_point
system_clock

system_clock is useful when you need to correlate the time with a known epoch so you can convert it to a calendar time

system_clock为系统时间(壁钟时间),它的原点时间是UNIX 1979-01-01 00:00::00 UTC,它包括3个静态方法:now,to_time_t,from_time_t(与C 风格的时间相互转换) 用法比较简单,当需要获取时间时,就可以使用system_clock

  • 基本用法
#include <chrono>
#include <iostream>
#include <ctime>
using namespace std::chrono;

int main()
{
    //获取当前时间
    auto tp = std::chrono::system_clock::now();
    std::cout << tp.time_since_epoch().count() << std::endl;

    std::cout << "time "<<time(NULL) << std::endl;

    //转换为time_t
    time_t now = std::chrono::system_clock::to_time_t(tp);
    std::cout << "当前时间 " << ctime(&now) << std::endl;

    //格式化时间
    tm *info = localtime(&now);
    char timefmt[128] = { 0 };
    sprintf(timefmt, "%d-%d-%d %d:%d:%d", info->tm_year + 1900, info->tm_mon + 1, info->tm_mday, info->tm_hour, info->tm_min, info->tm_sec);
    std::cout << timefmt << std::endl;
    
}

system_clock::now()返回一个时间点类型,通过to_time_t转为从1970 01-01 00:00:00到当前的计数(计数周期是有由period成员指定),再通过localtime格式化输出,但是通过该函数转换的时候,是将精度丢失了,转换后的精度是秒。

  • system_clock 也可以将任意时间点转换成时间
#include <chrono>
#include <iostream>
#include <ctime>
using namespace std::chrono;
int main()
{
    //system_clock的精度是100纳秒
    system_clock::duration d(10000000);
    std::cout << system_clock::duration::period::den << std::endl;

    system_clock::time_point epoch(d);
    time_t tmEpoch = std::chrono::system_clock::to_time_t(epoch);
    std::cout << "value is " << ctime(&tmEpoch) << std::endl;

    std::system("pause");
}

  • 如果改了系统时间,system_clock的值也会变化,所以它是不"稳定的",system_clock::is_steadyfalse
steady_clock

steady_clock is useful when you need to wait for a specific amount of time. steady_clock time can not be reset. As other steady clocks, it is usually based on the processor tick.

  • steady_clock是处理器的tick时间(非壁钟时间),steady_clock::now()返回的值绝不会小于上一次调用该方法时的值,即使改了系统时间,也会保证steady_clock::now产生正确的值

  • 它最适合度量间隔(精度)。它只有一个静态now方法,它经常被用来计算时间间隔,通过使用steady_clock域的duration和time_point,来表示高精度的时间间隔

#include <chrono>
#include <iostream>
#include <thread>
using namespace std::chrono;
int main()
{
    //时间点
    steady_clock::time_point s = steady_clock::now();
    std::this_thread::sleep_for(milliseconds(10));
    //时间点
    steady_clock::time_point e = steady_clock::now();

    //时间点-时间点为时间间隔
    steady_clock::duration d = e - s;
    std::cout << d.count() << std::endl;
    std::system("pause");
}

  • 更改了系统时间并不会影响 steady_clock,所以它是”稳定的“,steady_clock::is_steady值为true
high_resolution_clock

When available, high_resolution_clock is usually more expensive than the other system-wide clocks, so they are used only when the provided resolution is required to the application.

它是代表了当前系统的所支持的最高精度,是类型别名(可能是system_clock,也可能是steady_clock不同平台实现不同),它也只有一个now方法。使用场景跟steady_clock一致。

三种clock的now的输出
#include <chrono>
#include <iostream>
using namespace std::chrono;
int main()
{
    //system_clock
    system_clock::time_point st = system_clock::now();
    std::cout << st.time_since_epoch().count() << std::endl;

    //steay_clock
    steady_clock::time_point ste = steady_clock::now();
    std::cout << ste.time_since_epoch().count() << std::endl;
    
    //high_resolution_clock
    high_resolution_clock::time_point ht = high_resolution_clock::now();
    std::cout << ht.time_since_epoch().count() << std::endl;

    std::system("pause");
}

system_clock是系统时间,steady_lockhigh_resolution_clock是系统运行时间(在vs2017 上 high_resolution_clock就是steady_clock)

三种clock的精度

在每个clock类型都一个period的成员变量,它是一个ratio类型(比率),以一秒为分子,精度为分母,可以通过打印period的den成员值来确定clock的精度,如下:

#include <chrono>
#include <iostream>
using namespace std::chrono;
int main()
{
    std::cout << system_clock::period::den << std::endl;//输出100000000
    std::cout << high_resolution_clock::period::den << std::endl;//输出1000000000
    std::cout << steady_clock::period::den << std::endl;//输出1000000000

    std::cout << (double)system_clock::period::num / (double)system_clock::period::den << std::endl;
    //1e-07
    std::cout << (double)high_resolution_clock::period::num / (double)high_resolution_clock::period::den << std::endl;
    //1e-09
    std::cout << (double)steady_clock::period::num / (double)steady_clock::period::den << std::endl;
    //1e-09
    std::system("pause");
}

上面的代码是在vs2015上编译测试的结果,system_clock精度为100纳秒,high_resolution_clock精度为1e-09(应该是基于tick),steady_clock精度为1e-09(应该是基于tick)。精度取决于系统的支持,可能在linux下精度会不同了。

三种clock的小结

取系统时间(壁钟时间)应该使用system_clocksteay_clock用于度量间隔,high_resolution_clock使用场景跟steady_clock,因为在实现中它可能就是steady_clock的一个别名。

三种不同类型的clock不同精度的度量间隔,下面的代码如果通过system_clock度量间隔,精度就达不到了

#include <chrono>
#include <iostream>
using namespace std::chrono;
int main()
{
    //system_clock::time_point s = system_clock::now();
    //high_resolution_clock::time_point s = high_resolution_clock::now();
    steady_clock::time_point s = steady_clock::now();
    for (int i = 0; i < 10; ++i)
    {

    }

    //system_clock::time_point s1 = system_clock::now();
    //high_resolution_clock::time_point s1 = high_resolution_clock::now();
    steady_clock::time_point s1 = steady_clock::now();
    auto i = s1 - s;
    std::cout << i.count() << std::endl;
    std::system("pause");
}

Duration

std::ration

在介绍duration前,先要介绍std::ratio

template<

    std::intmax_t Num,
    std::intmax_t Denom = 1
> class ratio;

std::ratio代表一个比例,Num是分子,Denom是分母, Denom 不可为零且不可等于最负的值。代表的数值约分分子和分母到最简

C++11 标准中的对Duration的定义

template <class Rep, class Period = ratio<1> >
class duration;

A duration object expresses a time span by means of a count and a period.

它代表的是一个时间间隔,Period就是精度(比如秒ration<1>,毫秒ration<1,1000>),一个ratio类型,ratio以一秒为分子,精度为分母,它支持算术运算符,支持比较运算符

使用基本的时间单元

chrono库中,预定义了六种时间单元:hours,minutes,seconds,milliseconds,microseconds,nanoseconds,可以拿来直接使用:

//using seconds = duration<long long>
seconds s(3);//代表3秒钟
seconds s(1);//代表1秒钟

//using minutes = duration<int, ratio<60>>
minutes m1(3); //代表3分钟
minutes h1(60); //代表60分钟

//using milliseconds = duration<long long, milli>;
milliseconds ms(10); //代表10毫秒
milliseconds ms(1000); //代表1000毫秒即1秒

定义有含意的时间单元

在duration中预定义的时,分,秒等,本质上是类型别名,也可以通过这种方式定义在业务系统中有意义的时间单元,比如,在音视频系统,在编码端会设置采集或编码的帧率,比如设置为25帧,可以理解成每40ms产生一帧数据(采集或编码),那么通过duration定义40ms的时间单元

//在25帧的情况下,代表每帧的时间间隔即为40ms(ration<1,25>)

typdef std::chrono::duration<long,std::ratio<1,25>> FrameRate;

//代表一帧的时间间隔
FrameRate(1)

时间点

time_point表示时间点,在前面提到的公式,将时间点加减一个时间间隔还是一个时间点,可以通过clock将时间点换算成具体时间

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

一个转换的例子:

#include <chrono>
#include <iostream>
#include <ctime>
using namespace std::chrono;

int main()
{
    //定义了一个时间点,代表从system_clock的纪元后的一秒钟
    time_point <system_clock, duration<int>> tp_seconds(duration<int>(1));

    //将time_point转换为clock
    system_clock::time_point tp(tp_seconds);

    //格式化输出
    std::cout << tp.time_since_epoch().count() << std::endl;

    std::time_t tt = system_clock::to_time_t(tp);
    std::cout << "time_point tp is: " << ctime(&tt);
    std::system("pause");
}

duration_cast

duration_cast可以转化不同类型的时长,比如将分转化成秒

  • 在源duration能准确地为目标duration所整除的场合(例如小时到分钟),无需 duration_cast

  • 浮点时长和整数时长间转型能隐式进行,无需 duration_cast

#include <iostream>
#include <chrono>
#include <ratio>
#include <thread>

void f()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();
    f();
    auto t2 = std::chrono::high_resolution_clock::now();
    //浮点duration,不需要duration_cast
    std::chrono::duration<double,std::milli> f = t2 - t1;
    std::cout << f.count() << std::endl;

    //整型duration,需要duration_cast
    auto int_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
    std::cout << int_ms.count() << std::endl;

    //从毫秒到秒,需要duration_cast
    std::chrono::seconds int_sec = std::chrono::duration_cast<std::chrono::seconds>(int_ms);
    std::cout << int_sec.count() << std::endl;
    
}

格式化system_clocknow方法到微秒

#include <chrono>
#include <iostream>
using namespace std;
using namespace chrono;
int main()
{
    //获取当前时间
    auto tp = std::chrono::system_clock::now();
    //从纪元时间到现在的毫秒数
    auto md = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch());
    long ms = md.count() % 1000;


    //格式化时间
    time_t now = std::chrono::system_clock::to_time_t(tp);
    tm *info = localtime(&now);
    char timefmt[128] = { 0 };
    sprintf(timefmt, "%d-%d-%d %d:%d:%d %d", info->tm_year + 1900, info->tm_mon + 1, info->tm_mday, info->tm_hour, info->tm_min, info->tm_sec, ms);
    std::cout << timefmt << std::endl;
}
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo4776

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值