讀《Efficient C++》疑惑 (轉)

gugu99發表於2007-08-15
讀《Efficient C++》疑惑 (轉)[@more@]

  當我們進行開發時,如果程式碼比較少,我們可以很容易的掌握、瞭解的情況,但是當程式碼超過數千行,特別是達到上萬行的時候,我們就很難準確掌握程式的流程,在這種情況下,進行程式碼跟蹤是很重要的一件事情。
  程式碼跟蹤技術,對於大多數程式設計師來講,就是定義一個比較簡單的Trace類,將程式的資訊進行輸出,一般是在程式的入口寫一條資訊,在程式的出口寫一條資訊,雖然這是以時間為代價,但是它有助於我們在不使用器的情況下找到問題所在。
  最極端的情況就是透過#ifdef開關,徹底消除效能開銷,但是要像開啟/關閉跟蹤,必須重新編譯,顯然程式的最終無法這麼做。所以只有透過動態的與程式通訊來進行跟蹤控制。
  首先,為了能夠獲得程式執行時間,我們先自己定義一個簡單的測試效能的類,名字就叫做Timer,實現如下:
  class Timer
  {
  public:
  Timer():start(clock()) {}
  ~Timer() { cout<  private:
  clock_t start;
  };
  接下來,我們就要定義一個簡單的跟蹤類Trace,初步實現如下:
  class Trace
  {
  public:
  Trace(const string& name);
  ~Trace();
  static bool trace_active;
  private:
  string theName;
  };

  bool Trace::trace_active=false;

  inline Trace::Trace(const string& name):theName(name)
  {
  if(trace_active)
  cout<  }

  inline Trace::Trace(const char* name):theName(name)
  {
  if(trace_active)
  cout<  }

  inline Trace::~Trace()
  {
  if(trace_active)
  cout<  }
  接下來我們進行測試,先定義一個簡單的:int f(int x) { return x+1; }
  先測試不跟蹤的時間開銷:
  int main()
  {
  Timer* time=new Timer;
  Trace::trace_active=false;
  for(int i=0; i<1000000; ++i)  f(i);
  delete time;
 
  return 0;
  }
  輸出時間是30ms。
  接下來,我們開啟跟蹤功能: int f(int x) { Trace trace("f"); return x+1; } Trace::trace_active=false;
  同時把I/O關閉,再次測試,結果如下:1892ms 
  這裡我們看到了時間提升了62倍,主要的效能來源就是(1)在建構函式中"f"要轉換成string型別,(2)theName的構造和析構。似乎各自影響了1/3的,是不是這樣呢?
  下面,我們取消"f"要轉換成string型別的開銷:增加一個建構函式:
  Trace(const char* name):theName(name)
  {
  if(trace_active)
  cout<  }
  再次進行測試,結果如下(關閉I/O):1690ms 
  效能的提高似乎並不像我們預期的那樣高,為什麼呢?難大是if(trace_active)影響了測試結果?我們再把這條判斷語句關閉,再次進行測試,結果如下:1646ms。
  還是差了一些,並不是1/3的資料,是不是引數傳遞的影響?這次我們把theName也拿掉,看看類本身到底佔用了多少時間,再次測試,結果如下:153ms。
  分析以上資料:類本身的引數傳遞等佔用了153ms,if判斷語句佔用了44ms,theName的構造和析構佔用了1493ms,"f"轉換成string佔用了202ms,所以我們的主要目標應該集中在theName上面,下面我們用組合取代聚合,看看效能的變化:
  class Trace
  {
  public:
  Trace(const string& name);
  ~Trace();
  static bool trace_active;
  private:
  string* theName;
  };

  bool Trace::trace_active=false;

  inline Trace::Trace(const string& name)
  {
  if(trace_active)
  {
  cout<  theName=new string(name);
  }
  }

  inline Trace::Trace(const char* name)
  {
  if(trace_active)
  {
  cout<  theName=new string(name);
  }
  }

  inline Trace::~Trace()
  {
  if(trace_active)
  {
  cout<  delete theName;
  }
  }
  測試結果是2682ms,和我們採用聚合相比,時間增加了58.70%,看來在堆上建立開銷的確很大。但是書上說,這時候,時間反而減低了一個數量級,這是我不能夠理解的,如果有誰看過這本書,煩請告知一下,謝謝了!

  結論:從上面的分析,我們可以看出對於一個很小的函式,更總的開銷相對來說很大,影響了程式的效能。所以適合內聯的,就不適合跟蹤,Trace物件不應該新增到頻繁使用的小函式中。

  注:以上資料是在dev-C++ 4.9.5.0,下,每組資料測試了10次,取平均值得到的。另外,在上面的資料測試過程中,我始終沒有開啟I/O,原因是I/O太消耗時間了,不信,你開啟看看。
 
 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-958701/,如需轉載,請註明出處,否則將追究法律責任。

相關文章