http://blog.chinaunix.net/uid-25872711-id-3013832.html
Efficient C++ 第一章
Charpter 1 The tracing war story
很多时候,为了方便查找bug,会使用trace。比如用如下方式:
#ifdef TRACE Trace t("myFuction"); // Constructor takes a function name argument t.debug("Some information message"); #endif
这样的方式不错,但是问题是:每次TRACE宏的开头都要重新编译代码。想到用如下方式避免这一问题:
void Trace::debug(string &msg) { if (traceIsActive) { // log message here } }
加入一个traceIsActive变量来控制log开关,因为写log开销最大,这样看上去也算可行。下面看,假设有如下调用:
t.debug("x = " + itoa(x)); // itoa() converts an int to ascii
即使,traceIsActive为false,上面的调用还是会产生如下开销:
1) 创建一个临时string变量保存“x=”
2) 调用itoa()函数
3) 创建一个临时string变量保存itoa(x)返回的char*指针
4) 连接两个字符串,组成第三个临时string变量
5) debug return后,销毁所有临时变量
这样下来对程序原有执行是大有影响的,尤其是在程序常用接口中加入TRACE,那将严重影响执行效率。
最开始的实现
我们实现的trace主要在函数调用开始,调用结束,和中间部分写log信息。
class Trace { public: Trace (const string &name); ~Trace (); void debug (const string &msg); static bool traceIsActive; private: string theFunctionName; }; inline Trace::Trace(const string &name) : theFunctionName(name) { if (TraceIsActive){ cout << "Enter function" << name << endl; } } inline void Trace::debug(const string &msg) { if (TraceIsActive){ cout << msg << endl; } } inline Trace::~Trace() { if (traceIsActive) { cout << "Exit function " << theFunctionName << endl; } } //调用 int myFunction(int x) { string name = "myFunction"; Trace t(name); ... string moreInfo = "more interesting info"; t.debug(moreInfo); ... }; // Trace destructor logs exit event to an output stream
上面的trace实现,经过测试,程序加入trace前后,效率降低了20%之多!!!
问题在哪呢?
工程师对C++执行会有不同的理解,但是如下是最基本可知的原则:
1) I/0开销很大
2) 经常调用,而且很简短的函数声明为inline
3) 拷贝实例开销大,多传引用而不是实例
而我们的代码都符合3条原则,问题在于创建了过多我们并未真正使用的实例,下面是Trace的最小用例,程序调用开始和结束时写log记录。
int myFunction(int x) { string name = "myFunction"; Trace t(name); ... };
它包含了如下开销:
1)创建string实例name保存“myFunction”
2) 调用Trace的构造函数
3)Trace的构造函数调用string的构造函数,来初始化string成员
(函数调用结束时)
4)析构string实例name
5)调用Trace的析构函数
6)Trace析构函数调用string的析构函数,销毁string成员
实际上,当我们不要用Trace做log,上面这些实例都是完全无用的,这些开销白白浪费掉了。来做如下测试:
int addOne(int x) // Version 0 { return x+1; } int main() { Trace::traceIsActive = false;//Turn tracing off //... GetSystemTime(&t1); // Start timing for(i =0; i < j; i++) { y = addOne(i); } GetSystemTime(&t2); // Stop timing // ... }
我们将函数修改为如下,对比前后执行时间:
int addOne(int x) // Version 1. Introducing a Trace object { string name = "addOne"; Trace t(name); return x+1; }
执行结果显示,前后执行时间为55ms和3500ms,相差了超过60倍!!!!
The Recovery Plan
修改1,把addOne中创建的string实例name换成Char*,修改如下:
int addOne(int x) // Version 2. Forget the string object. // Use a char pointer instead. { char *name = "addOne"; Trace t(name); return x+1; } //做上面修改,同时Trace构造函数修改为: inline Trace::Trace(const char *name) : theFunctionName(name)// Version 2 { if (traceIsActive){ cout << "Enter function" << name << endl; } }
这样,我们去除了name string实例的创建和析构的开销,执行时间由3500ms降到2500ms。
下一步,去除Trace中string成员变量的不必要开销,成员变量用string指针代替,使用指针的好处是调用构造时,不会自动调用成员的构造函数,只是初始化指针值。我们可以辨别Trace状态,再调用构造函数。
class Trace { //Version 3. Use a string pointer public: Trace (const char *name) : theFunctionName(0) { if (traceIsActive) { // Conditional creation cout << "Enter function" << name < endl; theFunctionName = new string(name); } } ... private: string *theFunctionName; }; inline Trace::~Trace() { if (traceIsActive) { cout << "Exit function " << *theFunctionName << endl; delete theFunctionName; } }
通过这一步优化,Trace中string实例的不必要开销也消除了。执行时间降到了185ms。
Key Point
1)不要传实例,多用引用
2)常用的简单函数声明为inline
3)I/O开销大
4)尽量减小可能不被使用成员的调用构造、析构函数