2011-07-22 32 views
1

我有一個關於Sun JDK中提供的Java編譯器如何「聰明」的快速問題。具體來說,是否足夠聰明,可以提前評估出現在for()循環的條件部分中的任何函數,而不是在循環的每次迭代中對它們進行評估?Java控制結構中的自動編譯器優化?

例如,請考慮以下代碼。

// Array of doubles to "hold" the array. 
private double matrix[][]; 

public int getCols() { 
    // Compute the number of columns in a matrix. 
} 

public int getRows() { 
    // Compute the number of rows in a matrix. 
} 

// Compute the sum of all elements in the matrix. 
public double sum() { 

    double result = 0; 

    for (int r = 0; r < getRows(); r++) { 
     for (int c = 0; c < getCols(); c++) { 
      result += this.matrix[r][c]; 
     } 
    } 

    return result; 
} 

顯然,我可以修改的總和()方法來確保該GetRows的()和getCols()在循環的每一次迭代中,通過改變它不被評估以

public double sum() { 

    double result = 0; 
    int numRows = getRows(); 
    int numCols = getCols(); 

    for (int r = 0; r < numRows; r++) { 
     for (int c = 0; c < numCols; c++) { 
      result += this.matrix[r][c]; 
     } 
    } 

    return result; 
} 

不過,我想知道,如果編譯器足夠聰明,可以預先評估它們本身。也就是說,它是否會自動發現評估出現在條件中的任何函數在計算上更便宜,而不是在每次迭代中評估它們?

謝謝!

+0

我對此表示懷疑,因爲推斷狀態依賴性會帶來很大的收益,但爲什麼不使用數組的長度屬性?你當前的設置有點脆弱,因爲它隱含地假定所有行具有相同數量的列。 – Taylor

回答

5

通常,這樣的優化將是錯誤。這些方法是虛擬的,所以它們可以在一個子類中改變,做一些完全不同的事情(比如返回隨機數),並且可能很難不能通過靜態證明(是的,它需要證明)它們返回相同即使它們是final,也可以在每次迭代中獲得價值。事實上,也許他們根本就沒有這樣做(想想另一個線程並行地改變行數/列數 - 是的,在這種情況下你遇到了很多其他問題,但仍然需要考慮)。除此之外,編譯器不需要優化任何東西:在運行時,JIT編譯器可以進行更多的優化。例如,它可以用內聯虛擬調用生成代碼,並且它可以(取決於代碼)能夠分解錯誤(至少如果它們無條件返回常量)。但是,如果不能改變語義,那麼它不會這樣做。如果真的很重要,請自己動手。無論哪種方式,檢查它是否是瓶頸。

+0

+1這麼多好點。我只想指出,這種所謂的「優化」在很多語言中都是錯誤的,而不僅僅是Java。基本上任何允許副作用的語言。 – Perception

+0

@delnan謝謝你。指出一些原因_why_期望編譯器執行通常不是一個合理的事情是非常有幫助的。 – pmcs

2

我不認爲編譯器可以如此聰明。它不知道如何實現方法getRows()。如果每次打電話時它會返回不同的值?

所以,底線。代碼的第二個版本要好得多。不僅從性能角度來看。它更具可讀性,我相信你在編碼時應該更喜歡這種方法。

我允許自己寫入for循環的唯一方法是調用length()數組的方法。數組不是一個「真正的」類,它的length()方法不是一個真正的方法,所以它的調用不會影響性能。

+0

+1:我同意,但它不是一個屬性的方法 - 只有上帝知道爲什麼:-) – home

0

如果編譯器只評估一次這些表達式,那將是非常可怕的,因爲您可能會遇到需要對它們進行多次評估的情況。 但你可以寫一個小測試:

public class Test { 

    private static int number = 5; 

    public static void main(String[] args) { 
     for(int i = 0; i < end(); i++) { 
      System.out.println(i); 
     } 
    } 

    public static int end() { 
     return number--; 
    } 

} 

輸出:

0 
1 
2 

這說明你的編譯器評估每一次的表達。

0

編譯器沒有進行優化,但是Sun/Oracle編譯器足夠聰明,可以內嵌最多兩個「虛擬」方法。根據吸氣劑的複雜性,它可能沒有什麼區別。

它應該是足夠聰明,讓你的循環一樣

for (int r = 0, rmax=getRows(), cmax=getCols(); r < rmax; r++) { 
    double[] cs = this.matrix[r] 
    for (int c = 0; c < cmax-1; c+=2) { 
     result += cs[c] + cs[c+1]; 
    } 
    if (cmax % 2 == 1) 
     result += cs[cmax-1]; 
} 

我已經看到了循環展開的代碼,以便它在一個執行兩個循環。

你可以打賭,有興趣下載在OpenJDK的調試版本,並使用-XX:+ PrintAssembly http://wikis.sun.com/display/HotSpotInternals/PrintAssembly

+1

謝謝!關於使用-XX檢查生成的代碼的提示將非常方便。 – pmcs

1

javac不優化這樣的事情。 JVM確實。

這取決於您的getRows()getCols()中有什麼。如果它們非常簡單,幾乎可以肯定它們將被JVM內聯;那麼JVM可以進一步優化,如果它可以斷定這些值不會改變,則調用它們一次,並調整結果。

例如,下面的代碼

for(int i=0; i<string.length(); i++) 
    char c = string.charAt(i); 

將JVM進行積極的優化,通過我們無需手動優化可以戰勝它。