專家釋疑:輕鬆提高Java代碼的效能

來源:互聯網
上載者:User
效能

  尾遞迴轉換能加快應用程式的速度,但不是所有的 JVM 都會做這種轉換,很多演算法用尾遞迴方法表示會顯得格外簡明。編譯器會自動把這種方法轉換成迴圈,以提高程式的效能。但在 Java 語言規範中,並沒有要求一定要作這種轉換,因此,並不是所有的 Java 虛擬機器(JVM)都會做這種轉換。這就意味著在 Java 語言中採用尾遞迴表示可能導致巨大的記憶體佔用,而這並不是我們期望的結果。Eric Allen 在本文中闡述了動態編譯將會保持語言的語義,而靜態編譯則通常不會。他說明了為什麼這是一個重要問題,並提供了一段代碼來協助判斷您的即時(JIT)編譯器是否會在保持語言語義的同時做尾遞迴代碼轉換。

  尾遞迴及其轉換

  相當多的程式包含有迴圈,這些迴圈啟動並執行時間佔了程式總已耗用時間的很大一部分。這些迴圈經常要反覆更新不止一個變數,而每個變數的更新又經常依賴於其它變數的值。

  如果把迭代看成是尾遞迴函式,那麼,就可以把這些變數看成是函數的參數。簡單提醒一下:如果一個調用的傳回值被作為調用函數的值立即返回,那麼,這個遞迴調用就是尾遞迴;尾遞迴不必記住調用時調用函數的上下文。 

  由於這一特點,在尾遞迴函式和迴圈之間有一個很好的對應關係:可以簡單地把每個遞迴調用看作是一個迴圈的多次迭代。但因為所有可變的參數值都一次傳給了遞迴調用,所以比起迴圈來,在尾遞迴中可以更容易地得到更新值。而且,難以使用的 break 語句也常常為函數的簡單返回所替代。  

  但在 Java 編程中,用這種方式表示迭代將導致效率低下,因為大量的遞迴調用有導致堆疊溢位的危險。  

  解決方案比較簡單:因為尾遞迴函式實際上只是編寫迴圈的一種更簡單的方式,所以就讓編譯器把它們自動轉換成迴圈形式。這樣您就同時利用了這兩種形式的優點。  

  但是,儘管大家都熟知如何把一個尾遞迴函式自動轉換成一個簡單迴圈,Java 規範卻不要求做這種轉換。不作這種要求的原因大概是:通常在物件導向的語言中,這種轉換不能靜態地進行。相反地,這種從尾遞迴函式到簡單迴圈的轉換必須由 JIT 編譯器動態地進行。  

  要理解為什麼會是這樣,考慮下面一個失敗的嘗試:在 Integers 集上,把 Iterator 中的元素相乘。  

  因為下面的程式中有一個錯誤,所以在運行時會拋出一個異常。但是,就象在本專欄以前的許多文章中已經論證的那樣,一個程式拋出的精確異常(跟很棒的錯誤類型標識符一樣)對於找到錯誤藏在程式的什麼地方並沒有什麼協助,我們也不想編譯器以這種方式改變程式,以使編譯的結果代碼拋出一個不同的異常。  

  清單 1. 一個把 Integer 集的 Iterator 中的元素相乘的失敗嘗試 

import java.util.Iterator; 

public class Example { 

  public int product(Iterator i) { 
    return productHelp(i, 0); 
  } 

  int productHelp(Iterator i, int accumulator) { 
    if (i.hasNext()) { 
      return productHelp(i, accumulator * ((Integer)i.next()).intValue()); 
    } 
    else { 
      return accumulator; 
    } 
  } 

  注意 product 方法中的錯誤。product 方法通過把 accumulator 賦值為 0 調用 productHelp。它的值應為 1。否則,在類 Example 的任何執行個體上調用 product 都將產生 0 值,不管 Iterator 是什麼值。  

  假設這個錯誤終於被改正了,但同時,類 Example 的一個子類也被建立了,如清單 2 所示: 

  清單 2. 試圖捕捉象清單 1 這樣的不正確的調用 

import java.util.*; 

class Example { 

  public int product(Iterator i) { 
    return productHelp(i, 1); 
  } 

  int productHelp(Iterator i, int accumulator) { 
    if (i.hasNext()) { 
      return productHelp(i, accumulator * ((Integer)i.next()).intValue()); 
    } 
    else { 
      return accumulator; 
    } 
  } 


// And, in a separate file: 

import java.util.*; 

public class Example2 extends Example { 
  int productHelp(Iterator i, int accumulator) { 
    if (accumulator < 1) { 
      throw new RuntimeException("accumulator to productHelp must be >= 1"); 
    } 
    else { 
      return super.productHelp(i, accumulator); 
    } 
  } 

  public static void main(String[] args) { 
    LinkedList l = new LinkedList(); 
    l.add(new Integer(0)); 
    new Example2().product(l.listIterator()); 
  } 

  類 Example2 中的被覆蓋的 productHelp 方法試圖通過當 accumulator 小於“1”時拋出運行時異常來捕捉對 productHelp 的不正確調用。不幸的是,這樣做將引入一個新的錯誤。如果 Iterator 含有任何 0 值的執行個體,都將使 productHelp 在自身的遞迴調用上崩潰。  

  現在請注意,在類 Example2 的 main 方法中,建立了 Example2 的一個執行個體並調用了它的 product 方法。由於傳給這個方法的 Iterator 包含一個 0,因此程式將崩潰。  

  然而,您可以看到類 Example 的 productHelp 是嚴格尾遞迴的。假設一個靜態編譯器想把這個方法的本文轉換成一個迴圈,如清單 3 所示:  

  清單 3. 靜態編譯不會最佳化尾調用的一個樣本

int productHelp(Iterator i, int accumulator) { 
    while (i.hasNext()) { 
      accumulator *= ((Integer)i.next()).intValue(); 
    } 
    return accumulator; 
  } 

  於是,最初對 productHelp 的調用,結果成了對超類的方法的調用。超方法將通過簡單地在 iterator 上迴圈來計算其結果。不會拋出任何異常。  

  用兩個不同的靜態編譯器來編譯這段代碼,結果是一個會拋出異常,而另一個則不會,想想這是多麼讓人感到困惑。



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.