在对程序进行性能评估时, 需要计算某个函数的执行耗时。思路是利用c++的RAII,在函数执行结束时,自动计算耗时并打印。
class Timer
{
public:
Timer(const char* funcName)
{
_funcname = funcName;
_begin = clock();
}
~Timer()
{
_end = clock();
_elapsed = _end - _begin;
printf("Function name: %s\nElapsed : %f\n", _funcname ,_elapsed);
}
private:
double _begin;
double _end;
double _elapsed;
const char* _funcname;
};
下面是这个类的用法。
void function()
{
Timer timer(__FUNCTION__);
// Do your calculation
}
如你所见, 只需要把这个对象放在函数的最开始就可以了。 当程序的执行流程离开这个函数时, timer
对象会被析构, 析构函数会被调用, 这样一来, 写在析构函数里的计时代码也就会被执行了。
但是这种做法难免有点丑陋,不妨用宏处理一下。 对需要计算耗时的函数,只要在函数首行写一个 marker 就可以了。也就是说,我想达到这样的效果:
void function()
{
PROFILE_THIS();
// Do your calculation
}
同时,如果function
函数中出现了其他叫timer
的变量,就会发生冲突。 这需要对变量名进行混淆。我的处理是在timer
后面加当前行号。 为了做这件事,我们需要在预处理阶段进行符号拼接。
#define PROFILE_THIS_IMPL(funcname) Timer CONCAT(_timer, __LINE__)(funcname)
#define PROFILE_THIS() PROFILE_THIS_IMPL(__FUNCTION__)
CONCAT
是用于符号拼接的宏函数,如果当前的行号是43,那么这个宏代换后的结果是Timer _timer43(__FUNCTION)
。 CONCAT
的实现,并没有那么简单,这里有一个坑要踩。我们先看它的实现。
#define CONCAT_IMPL(x, y) x##y
#define CONCAT(x, y) CONCAT_IMPL(x, y)
这里竟然多套了一层。 如果直接写CONCAT(x, y) x##y
会发生什么呢?请看下面的代码
#define CONCAT(x, y) x##y
CONCAT(a, __LINE__) // 结果是a__LINE__
也就是说,__LINE__
这个宏并不会被预处理器替换。所以这里多加了一层的目的就是要让其他宏符号被替换。
在最终的Release版本中, 我们不再需要计算函数耗时。我希望对代码进行编译控制,也就是说,在Release模式下, PROFILE_THIS();
失效, 那么我们只需要在Release模式下,使这个宏为空就好了。
#ifdef PROFILE
# define PROFILE_THIS() PROFILE_THIS_IMPL(__FUNCTION__)
#else
# define PROFILE_THIS()
#endif