Efficient C++ 第一章

转自
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)尽量减小可能不被使用成员的调用构造、析构函数

猜你喜欢

转载自jacky-dai.iteye.com/blog/2307340