軟體的效能設計(二) 臨時物件對軟體效能的影響 (轉)

worldblog發表於2007-12-07
軟體的效能設計(二) 臨時物件對軟體效能的影響 (轉)[@more@]

  的設計(二) 臨時對軟體效能的影響
  劉彥清·yesky

   臨時物件的存在時間一般都比較短暫,除了作為其他資料的容器外,沒有其他什麼用途,開發人員一般用它向方法傳遞資料或從方法中返回資料。文章的第一部分探討了建立臨時物件是如何影響效能的,並表明恰當的類的介面設計可以有效地減少臨時物件的建立。透過避免設計這樣的介面,就可以減少臨時物件的建立,降低對程式效能的影響程度。在本篇文章中,我將討論過多地建立臨時物件的問題並在後面的文章中提供一些成熟的技術來避免過多地建立臨時物件。

  僅僅對String說NO?

  說到建立臨時物件,String類是最大的"罪魁禍手"。為了說明這一點,我在這篇文章的第一部分中開發了一個匹配類的例子,並演示了一個看起來頗為正常的介面是如何因為建立了臨時物件而比一個具有較好介面的類似的類執行速度慢數倍的。下面是最初的和效能較好的類的介面:

  BadRegExpMatcher

  public class BadRegExpMatcher {

   public BadRegExpMatcher(String regExp);

   /** 把輸入文字與特定的表示式進行匹配,如果匹配則返回匹配的文字,否則返回空字元 */

   public String match(String inputText);

  }

  BetterRegExpMatcher

  class BetterRegExpMatcher {

   public BetterRegExpMatcher(...);

   /** 向匹配子程式提供多種格式的輸入━━String、字元陣列和字元陣列的子集。如果不匹配則返回-1,如果匹配,則返回匹配開始處的偏移量 */

   public int match(String inputText);

   public int match(char[] inputText);


   public int match(char[] inputText, int offset, int length);

   /** 如果匹配,則返回匹配的長度,程式可以從返回的匹配開始處偏移量和長度重新構造匹配的文字 */

   public int getMatchLength();

   /** 如果呼叫程式需要,這個例程可以很方便地構造出匹配字串 */

   public String getMatchText();

  }

  大量使用BadRegExpMatcher的程式要比使用BetterRegExpMatcher的程式執行速度慢一些。第一,呼叫程式必須建立String物件向match()傳遞引數,match()也必須建立一個String物件向呼叫程式返回匹配的文字。每次呼叫時都會建立二個物件,這聽起來也許沒有什麼大問題,但如果頻繁地呼叫match(),建立這二個物件對效能產生的影響就大了。使用BadRegExpMatcher的程式的效能問題並不源於其編碼而源於其介面,象這樣設計的介面,臨時物件的建立是不可避免的。

  BetterRegExpMatcher用比較簡單的資料型別(整型、字元陣列)取代了在match()中使用的String物件,從而無需在呼叫程式和match()之間透過中間物件傳遞資料。

  由於在設計階段比在完成整個程式後再進行修改能夠更好地避免程式效能方面的問題,因此應該在類的介面如何處理物件的建立這個問題上多花些時間。在RegExpMatcher中,其方法要求輸入和返回String物件就可能對效能有潛在的影響,因為String類的物件是不可變的,因此對String類物件引數進行處理就會要求在每次呼叫時建立一個新的String物件。

  由於不可變性通常與額外的物件建立聯絡在一起━━這大部分原因都要"歸功"於其不可變性,許多人員就斷定不可變的物件一定會影響程式的效能。其實真實的情況要複雜得多,實際上,不可變性有時還能夠提升程式的效能,可變的物件也能夠引起程式效能的下降,可變性對程式效能的影響取決於其使用方式。

  程式會經常對文字字串進行操作和修改━━不可改變性確實是一個麻煩。在每次對String進行操作時━━例如查詢或選擇一個字首或子串,把它轉換為大寫或小寫,或者將二個字串合併成一個新的字串時,就必須建立一個新的String類物件。

  另一方面,我們可以自由地共享一個不可變物件的地址而無需擔心物件會被改變,此時,不可變物件在效能上就比可變物件要好許多。
可變物件也存在臨時物件問題

  在RegExpMatcher中,當一個方法返回的資料型別為String類時,就有必要建立一個新的String類物件。在BadRegExpMatcher中存在的問題之一是match()返回的是一個物件而不是一個簡單型別的資料━━因為一個方法返回一個物件,並不意味著一定會建立一個新的物件。考慮一下Point和Rectangle等.awt中的幾何類,一個Rectangle只不過是由四個整數━━左上角點的X、Y座標以及寬度和高度組成的,AWT類了元件的位置並透過getBounds()方法將它作為一個Rectangle類物件返回:

  public class Component {
   ...
  public Rectangle getBounds();
  }

  在上面的例子中,getBounds()方法僅僅起一個輔助性作用,它只是宣告一些元件內部的有關資訊。getBounds()真的必須建立它返回的Rectangle物件嗎?也許是這樣的吧,我們來看一下getBounds()的編碼:

  public class Component {
   ...
  protected Rectangle myBounds;

  public Rectangle getBounds() { return myBounds; }
  }

  當有程式呼叫上面例子中的getBounds()時,並不會建立新的物件,因為元件已經知道它的位置,因此getBounds()是比較高效的。然而,Rectangle的可變性還引起了其他問題,當一個呼叫它的程式下面的程式碼時會出現什麼樣的情況呢?

  Rectangle r = component.getBounds();
   ...
   r.height *= 2;

  因為Rectangle具有可變性,上面的程式碼將引起元件的改變,對於象AWT這樣的GUI工具包而言,這將是災難性的,因為當一個元件變化時,需要重新重新整理螢幕,同時還需要通知事件監視程式。因此上面的Component.getBounds()的執行是相當危險的,下面所示的方式才是比較的:

  public Rectangle getBounds() {
   return new Rectangle(myBounds.x, myBounds.y,
   myBounds.height, myBounds.width);
  }

  但是,就象RegExpMatcher那樣,每次呼叫getBounds()都會建立一個新的物件,下面的程式碼將會建立四個臨時物件:

  int x = component.getBounds().x;
  int y = component.getBounds().y;
  int h = component.getBounds().height;
  int w = component.getBounds().width;

  對於String類而言,建立物件是必要的,因為String是不可變的。但是在這個例子中,建立臨時物件似乎也是必需的,因為Rectangle具有可變性,我們可以透過不在介面中使用任何物件來避免象String引起的那樣的問題。儘管在與RegExpMatcher類似的場合中,這一方案並非總是可行的或理想的,然而,幸運的是,在設計類時可以採用一些技術,既能使用小一些的物件又不會遇到使用太多的小物件所引起的問題。


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

相關文章