Gear with Code Java Engineer

C++记录一个函数运行的时间

2019-11-20
C++

昨天接到一个活,让我把项目中各个过程消耗的时间写到日志里。这个问题不困难,但是很麻烦。第一需要对项目的整个流程大致了解才行;第二需要修改的地方太多,需要在每个过程的开头开始计时,最后在结束后停止计时。经过一番修改,最终找到了一个还算不错的解决办法,且听我细细道来。

1 C++计时方法

说到计时,无非就是在开始的时候获取一个时间点,结束后获取一个时间点,然后将二者相减。

#include <time.h>
int main(){
    // 获取开始时间
    clock_t before = clock(); // clock() 可以获取程序开始执行到现在经历的时间,单位为毫秒,clock_t就是long

    // 过程代码
    // ...

    // 获取结束时间
    clock_t after = clock();
    
    // 计算经历时间
    clock_t during = after - before;
    
    // 将经历时间转为字符串
    during %= (1000 * 3600);

	int m = during / 1000 / 60;
	during %= (1000 * 60);

	int s = during / 1000;
	during %= 1000;

	int ms = during;

	std::stringstream stm;
	stm << std::setfill('0');
	stm << h << "H ";
	stm << m << "M ";
	stm << s << "S ";
	stm << ms << "MS";
    
    cout << stm.str();
}

计时的原理一定是这样,但是在实际运用的过程中则非常的不方便。

  1. 如果一个计时过程有大量的代码,需要人工找到过程的起始点和终止点。
  2. 一个项目中需要计时的过程非常多。
  3. 每开始一个计时就需要定义一个计时变量,尤其是在一大段计时中又需要分几个小段的情况下,命名非常困难且容易冲突。
  4. 主业务代码中添加了很多计时用的代码,使代码变得混乱,主业务逻辑不清晰。

总之缺陷很多。

2 对每个函数进行计时

如果换一种思路,对每个函数进行计时,那么就不需要去区分哪些代码属于一个过程,并且一个函数只需要计时一次,也不会出现命名的困难。当然,如果代码的架构很好的话,本来就将各个过程都封装成了函数,这两个问题就成了一个问题。但是,对函数计时也存在一定的困难,请看下面的情况:

void function() {
    if (condition1)
    {
        // ...
        if (condition1)
        {
            // ...
            if (condition1)
            {
                // ...
                return;
            }
            else
            {
                return;
            }
        }
        else
        {
            return;
        }
    }
    else
    {
        return;
    }
}

也就是一个函数有很多地方有 return,那么如果我们就需要在每一个 return 前面加上结束计时的代码。这同样很麻烦,没有任何一个程序员喜欢把代码复制了到处粘贴的。

3 利用 try…catch…finally 语句对函数进行计时?

在遇到上面的问题后,我的第一反应就是可以以用 try…catch…finally 语句对函数进行计时,因为 finally 块中包含的语句即使函数返回了也会执行,只要在 finally 块中结束计时不久可以了吗?抱歉:

​ 1 IntelliSense: 未定义标识符 “finally” …….

标准 C++ 不支持 finally,WTF?

4 使用析构函数来实现

既然 C++ 不支持 finally ,那就想另想办法吧。我们的主要目的是:在函数退出时获取时间并打印计时。那么还有什么是在函数退出时发生的呢?(o゜▽゜)o☆[BINGO!],局部变量的析构发生在退出局部作用域时,简单地说,就是:

  1. 写一个类,在构造函数中获取开始时间。
  2. 在析构函数中获取结束时间,并计算经历时间,完成写日志工作。
  3. 在需要计时的函数开头声明一个此类的局部变量。

实现:

class CLogTime
{
private:
	long long startPoint;
	std::string statement;
public:
	CLogTime(CString file, int line, CString function); // 三个参数分表表示调用此函数处的文件名、行数、调用此函数的函数名。
	~CLogTime();
};

CLogTime::CLogTime(CString file, int line, CString function) 
{
	CString timeFunctionStr; 
	timeFunctionStr.Format(_T("FILE: %s,  LINE: %d,  FUNCTION: %s"), file, line, function); 

	USES_CONVERSION;
	statement = std::string(T2A(timeFunctionStr.GetBuffer()));
	startPoint = clock(); // 获得开始时间
}

CLogTime::~CLogTime()
{
	// 获得析构时间
	long long endPoint = clock();

	// 获得经历时间
	long during = endPoint - startPoint;

	int h = during / 1000 / 3600;
	during %= (1000 * 3600);

	int m = during / 1000 / 60;
	during %= (1000 * 60);

	int s = during / 1000;
	during %= 1000;

	int ms = during;

	std::stringstream stm;
	stm << std::setfill('0');
	stm << h << "H ";
	stm << m << "M ";
	stm << s << "S ";
	stm << ms << "MS";

	// 写入日志
	CLogManager::writeLog(statement.append(" cost time: ").append(stm.str()));
}

5 改进

虽然已经实现功能了,但是还需要改进一下:

  1. 把被计时的函数的函数名写入日志。
  2. 让调用形式更统一,更方便。

定义宏:

// 记录时间的宏,在函数的开头使用此宏,会将函数运行的时间写入日志
#define _TIME_THIS_FUNCTION_ \
CLogTime logFunctionTime(__FILEW__, __LINE__, __FUNCTIONW__);

这样在每个需要计时的函数开始加上

_TIME_THIS_FUNCTION_

就可以输出哪个文件,哪一行,哪个函数消耗了多少时间,效果如下。

2019-11-19 11:41:11 FILE: ReadBaseLineIDXtoBuildModel.cpp, LINE: 252, FUNCTION: ReadBaseLineIDX cost time: 0H 0M 0S 257MS


Comments

Content