軟件的性能設計(一)接口設計對軟件性能的影響

           軟件的性能設計(一)接口設計對軟件性能的影響 
       劉彥清·yesky

  性能方面的問題有好多種。最容易修正的一種是,在執行一項計算任務時使用了一個性能不好的算法,例如,在對數目很多的數據進行排序時採用了起泡算法,每次使用時對一個經常使用的數據項進行計算而不是將它保存起來,這些問題一般我們都能很容易發現,而且一旦發現後,都能很方便地進行改正。然而,許多Java程序性能方面的問題都是是由一些比較深奧的、不容易修改的代碼━━程序組件的接口設計引起的。

  大多數的程序都是由內部人員開發的或從外部購買的組件"組裝"而成的。即使軟件不完全依賴於原有的組件,面向對象的設計過程也使得應用程序在開發時採用組件形式,因爲這樣可以簡化程序的設計、開發和調試方面的工作。儘管採用組件的好處是不可否認的,我們還應該意識到組件的接口會對使用它們的程序的性能和運行狀態產生重大的影響。

  也許會有讀者問,接口跟性能有什麼關係?一個類的接口不但定義了類可以完成的功能,而且還定義了它的對象創建行爲和使用它所需要調用的方法的順序,一個類如何定義它的構造器和方法會影響這個對象是否可以重用,是它本身的方法創建還是要求其客戶創建中間對象,客戶要使用這個類需要調用多少個方法。

  所有這些因素都會影響到程序的性能。Java軟件性能管理方面的基本原理之一是:避免創建過多的對象。這並不意味着你不能創建任何對象從而不充分利用面象對象語言帶來的諸多好處,而是說在開發對性能敏感的代碼時需要對對象的創建保持謹慎。對象創建的代價相當高昂,我們應該在對性能敏感的軟件中儘量避免創建臨時或中間對象。

  在處理字符的程序中,String類是引起對象創建的最大源。因爲String類是不可變的,每當一個String類的對象被修改或構造時,都會創建一個新的對象。因此,一個具有性能意識的編程人員總是避免過多地使用String類對象。然而,儘管你在編程中儘量避免使用String對象,還是會經常發現使用的組件接口必須使用String對象,因此,你不可能不使用String類對象。

  例子:表達式的匹配

  作爲一個例子,可以假設你在編寫一個名字爲MailBot的郵件服務器。MailBot需要處理每個郵件頂部的MIME頭部━━例如發送日期或者發送者的郵件地址,它將通過使用一個匹配表達式的組件處理MIME頭部,以使這一處理過程會更簡單一些。它把輸入的字符放在一個字符緩衝區中,通過對緩衝區進行索引處理標題。由於MailBot將調用這一表達式匹配子程序來處理每一個標題,因此這個匹配子程序的性能將十分地重要。

  我們首先來看一個性能十分低下的表達式匹配類的接口:

  public class AwfulRegExpMatcher {

   /**創建一個給定表達式的匹配過程,它將對給定的字符串進行處理*/

   public AwfulRegExpMatcher(String regExp, String inputText);

   /**找到針對輸入文本的下一個匹配模式,如果匹配,返回匹配的文本,否則返回一個空字符 */

   public String getNextMatch();

   }

即使這個類採用了一個很高效的匹配算法,大量調用它的程序的性能也不會很好。因爲匹配器對象是與輸入文本捆綁在一起的,每次調用它時,都需要首先生成一個新的匹配器對象。由於我們的目標是減少不必要的對象創建工作,實現對匹配過程代碼的重用應該是一個良好的開端。

  下面的這個類定義了匹配器的另一種可能的接口,它允許匹配器重用,但性能仍然不夠好:

  public class BadRegExpMatcher {

   public BadRegExpMatcher(String regExp);

   /** 試圖針對輸入文本匹配指定的表達式,如果匹配則返回匹配的文本,否則返回一個空白字符串*/

   public String match(String inputText);

   /** 得到下一個匹配的字符,否則返回一個空白字符*/

   public String getNextMatch();

  }

避開返回的匹配子表達式等敏感的表達式匹配問題不談,這個類的定義有什麼問題嗎?如果僅僅從其功能方面看,它沒有任何問題,但如果從性能方面來考慮,則它存在許多問題。首先,匹配器要求其調用者創建一個String類來表示被匹配的文本。MailBot應該儘量避免生成String對象,但當它發現一個需要處理的標題時,它必須創建一個String對象供BadRegExpMatcher調用:

  BadRegExpMatcher dateMatcher = new BadRegExpMatcher(...);

   while (...) {

    ...

    String headerLine = new String(myBuffer, thisHeaderStart,

      thisHeaderEnd-thisHeaderStart);

    String result = dateMatcher.match(headerLine);

    if (result == null) { ... }

  }

  其次,即使MailBot僅僅需要得到是否匹配的返回信息,而無需得到匹配的文本,匹配器也會返回一個匹配的字符串。這意味着爲了簡單地使用BadRegExpMatcher來驗證一個特定格式的日期標題,你也必須創建二個 String對象━━供匹配器使用的輸入文本和匹配結果文本。創建二個對象似乎不會對性能產生重大影響,但如果必須爲MailBot處理的每條郵件的標題創建二個對象,就可能嚴重地影響程序的性能。這一問題並不出在MailBot本身的設計上,而是出在BadRegExpMatcher的設計上。

  注意:不返回String對象而返回一個"輕量級"的Match對象也不會在性能上帶來很大的改進。儘管創建一個Match對象的代價要比創建一個String對象的代價低一些,它還是會產生一個char數組,並拷貝數據,仍然創建了一個對調用者並非必需的臨時性的對象。

  BadRegExpMatcher只接受它需要的輸入數據類型,而不是可以接受我們方便提供的數據類型,僅就這一點,它就非常不理想。使用BadRegExpMatcher還會帶來別的危害,其中的一個潛在的危害是這樣將對MailBot的性能帶來更多的影響。儘管在處理郵件的標題時必須避免使用Strings,但又必須創建許多的Strings對象供BadRegExpMatcher使用,因此你可能放棄不使用String對象的目標,而更加不受限制地使用它。一個設計不恰當的組件會影響使用它的程序的性能,即使以後找到了一個無需使用String對象的表達式組件,整個程序仍然會受到影響。
 
一個恰當的接口

  如何定義BadRegExpMatcher才能避免上述的問題呢?首先,BadRegExpMatcher應該不指定其輸入文本的格式,它應該能夠接受其調用者可以高效地提供的任何一種數據類型。其次,它不應該爲匹配結果自動地生成一個String對象,只需要返回足夠的信息讓調用者來決定是否需要生成匹配結果字符串。(也可以提供一個方法來完成這一任務,但這並非是必需的。)一個性能比較好的接口應該是這樣的:

  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();

   }

  新的接口消除了調用者將輸入文本轉化爲匹配子程序所要求的格式的需求。MailBot可以用如下的方式調用match():

  int resultOffset = dateMatcher.match(myBuffer, thisHeaderStart, thisHeaderEnd-thisHeaderStart);

  if (resultOffset < 0) { ... }

  這樣就既達到了設計目標又沒有創建任何新的對象,另外,它的接口設計也體現了Java所倡導的"多而簡單的方法"的設計思想。

  創建對象對性能的精確影響取決於match()完成的工作量。通過創建和對二個不作任何實際工作的表達式匹配程序類的運行進行計時,就會發現它們在性能上存在着巨大的差異,在Sun 1.3 JDK中,使用BetterRegExpMatcher類的上述代碼的運行速度比使用BadRegExpMatcher類快50倍。通過簡單地支持子串匹配,BetterRegExpMatcher的運行速度就可以比BadRegExpMatcher快5倍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章