在生产系统上面,测量系统的运行性能,定位问题,都会用到一个参考值,就是某段代码对运行时间。这个功能时间也简单,就是在代码的开始位置以及结束位置各去执行一下时间获取的操作,然后求下得到的两个值的差值就能获得,像下面这代码一样
time_t beginTime = time();
func();
time_t endTime = time();
int32_t diff = endTime - beginTime;
逻辑确实非常简单,如果情况都像上面这样,只需要定位到函数粒度的话,问题还是还是相对容易处理的,但是如果要测量的下面这样每个实例代码中每个函数的耗时的话,情况就会相对复杂了,结束测量的埋点需要大量入侵到代码中,像下面这样
void func1(){
time_t beginTime = time();
if( func2() ){
time_t endTime = time();
int32_t diff = endTime - beginTime;
return;
}
if ( condition ){
time_t endTime = time();
int32_t diff = endTime - beginTime;
throw exception;
}
time_t endTime = time();
int32_t diff = endTime - beginTime;
}
这业务代码还没有埋点代码多。所以,我们需要做一个测量工具,至少需要解决下面两个痛点
- 自动结束,不管函数是被中间返回还是异常结束
- 尽量少入侵代码
如果这是在python中,可以做一个包装器来实现,但是c++没法实现包装器,怎么办呢?也许有同学会想到了,就是RAII。
在c++11之前,标准库一直没有提供时间相关的库实现,所以对于这种功能,我们一遍会使用linux的系统调用 gettimeofday() 来完成的,下面给一个简单的实例
#include <sys/time.h>
#include <iostream>
#include <string>
class ScopeTime{
public:
ScopeTime( const std::string& task ):mTask(task){
gettimeofday( &mBeginTime, NULL );
}
~ScopeTime(){
struct timeval endTime;
gettimeofday( &endTime, NULL );
std::cout << mTask <<" cost: "
<< (endTime.tv_sec-mBeginTime.tv_sec)*1000 + (endTime.tv_usec - mBeginTime.tv_usec)/1000
<< " ms" << std::endl;
}
private:
std::string mTask;
struct timeval mBeginTime;
};
int main(){
ScopeTime st("main");
do_something();
}
这对于没有系统移植需要,也不介意系统调用开销的应用而言,其实已经足够了,但是如果需要做到跨平台,这个简单封装就没有办法了。不过好在c++11提供了一套时间相关的库 chrono ,这个库包含了不少的功能点,在这里我就不去啰嗦太多了,大家可以自己点链接进去阅读官网,下面我们只是简单展示下怎样用这个库来解决我们时间测量工具不可移植的痛点。
#include <chrono>
#include <string>
using namespace std;
using namespace std::chrono;
class TimeMeasure{
public:
TimeMeasure( ):m_begin(high_resolution_clock::now()){
}
void reset() { m_begin = high_resolution_clock::now(); }
int64_t elapsed() const
{
return duration_cast<chrono::milliseconds>(high_resolution_clock::now() - m_begin).count();
}
int64_t elapsed_micro() const
{
return duration_cast<chrono::microseconds>(high_resolution_clock::now() - m_begin).count();
}
int64_t elapsed_nano() const
{
return duration_cast<chrono::nanoseconds>(high_resolution_clock::now() - m_begin).count();
}
int64_t elapsed_seconds() const
{
return duration_cast<chrono::seconds>(high_resolution_clock::now() - m_begin).count();
}
int64_t elapsed_minutes() const
{
return duration_cast<chrono::minutes>(high_resolution_clock::now() - m_begin).count();
}
int64_t elapsed_hours() const
{
return duration_cast<chrono::hours>(high_resolution_clock::now() - m_begin).count();
}
private:
time_point<high_resolution_clock> m_begin;
};
class ScopeTime{
public:
ScopeTime( const std::string& task):mTask(task){ mBegin = Singleton<TimeMeasure>::instance().elapsed(); }
~ScopeTime(){
std::cout << mTask <<" cost: " << Singleton<TimeMeasure>::instance().elapsed() - mBegin << " ms" << std::endl;
}
private:
std::string mTask;
int64_t mBegin;
};
int main(){
ScopeTime st("main");
do_something();
}
//Singleton 是一个非入侵的单例的包装类,大家可以自行实现,这里就不贴代码了。
//至于TimeMeasure不以成员变量的形式而是用一个单例呢?这是由于析构时首先会析构成员变量,所以在打印之前,成员变量可能已经被析构了。
//如果还是搞不明白的同学,自行复习c++的基础知识