2013-07-07 51 views
3

Java 5給了我們for-each循環,應該儘可能使用它。迭代數組的哪個習慣用法最有效?

但是,如果您需要使用塊中數組的索引,那麼最有效的習慣用法是什麼?

// Option (1) 
for (int i = array.length - 1; i >= 0; i--) { 
    // Code. 
} 

// Option (2) 
for (int i = 0; i < array.length; i++) { 
    // Code. 
} 

// Option (3) 
for (int i = 0, n = array.length; i < n; i++) { 
    // Code. 
} 

(很顯然,這不會使大多數程序,但幽默我有很大的性能差異。):-)

  1. 向後循環,是可怕的。甚至可能緩存不友好?或者現代化的處理器能夠檢測到內存中的回退?

  2. 更短,我可以看到JIT編譯器如何確定array從不改變,因此length是恆定的,因此它基本上可以用(3)代替它。但是這樣做嗎? (假設JVM是Oracle的Hotspot/Java 7)

  3. 由Joshua Bloch在項目45中建議的Effective Java作爲最快的選項,如果它是一些Collection.size()這是上限。但是它也適用於數組嗎?從字節碼(見下面)我可以看出,每個週期保存一個arraylength指令(預優化)。

This question有關的Dalvik虛擬機循環,列表(1) - (3)最快到最慢。然而,這些信息是從2008年開始的,今天Dalvik更加成熟,所以我幾乎認爲情況依然如此。

看着從上面的例子中生成的字節碼,有明顯的差異:

Compiled from "ForLoops.java" 
public class ForLoops extends java.lang.Object{ 
static int[] array; 

public ForLoops(); 
    Code: 
    0: aload_0 
    1: invokespecial #10; //Method java/lang/Object."<init>":()V 
    4: return 

public static void forLoop1(); 
    Code: 
    0: getstatic #17; //Field array:[I 
    3: arraylength 
    4: iconst_1 
    5: isub 
    6: istore_0 
    7: goto 13 
    10: iinc 0, -1 
    13: iload_0 
    14: ifge 10 
    17: return 

public static void forLoop2(); 
    Code: 
    0: iconst_0 
    1: istore_0 
    2: goto 8 
    5: iinc 0, 1 
    8: iload_0 
    9: getstatic #17; //Field array:[I 
    12: arraylength 
    13: if_icmplt 5 
    16: return 

public static void forLoop3(); 
    Code: 
    0: iconst_0 
    1: istore_0 
    2: getstatic #17; //Field array:[I 
    5: arraylength 
    6: istore_1 
    7: goto 13 
    10: iinc 0, 1 
    13: iload_0 
    14: iload_1 
    15: if_icmplt 10 
    18: return 

} 
+0

理論上,1和3在一般情況下效率更高,因爲'array.length'只需要進行一次評估。然而,在Java中評估'length'非常簡單,尤其是在JITCed的情況下,所以理論上的差異在實踐中幾乎消失了。最有可能取決於循環體中的內容以及JITC優化器如何將循環體與循環控制融合在一起。 –

+0

@Nicholas尼斯工作拉出字節碼!但重要的是要記住,由於JIT,字節碼並不是最終執行的。如果我們可以得到JIT指令,那*就是最終答案,但這可能是不現實的,因爲(a)它是平臺特定的,(b)JITing有不同的「級別」。另外,我不知道任何JIT會給運行時生成的代碼提供任何透明度,至少不是沒有自己在JVM內存空間中進行操作。 – sigpwned

+0

我當然應該指出,「最有效」的選擇通常是最適合其他代碼的選擇。例如,向後迭代有時很笨拙,有時非常有用。同樣,如果數組對象在循環內部被替換,在每次迭代中檢查'array.length'實際上可能是必需的。 –

回答

1

您可以輕鬆地測試自己這一點;如果你這樣做,你應該看看HotSpot創作者本身的these tips about performance testingThis answer也可能有用。如果您決定測試這些實現,請讓我們知道您發現了什麼!

但是,總的來說,你不應該擔心這些事情太多。相反,請專注於編寫可讀代碼並完成任務。大多數情況下,你會發現你的代碼運行得很快,沒有任何「技巧」。現代硬件非常快,而且JIT也很好。

如果你發現你的代碼運行速度太慢,配置文件首先,然後優化。還有什麼是不成熟的。請記住,從一個比我們任何人都更聰明的人:

「不成熟的優化是一切邪惡的根源。」 - 高德納

編輯:既然你似乎好奇這個少來講「應該怎麼我寫我的代碼?」更多的思想實驗方面,我想預計所有這些選項運行在或多或少相同的速度。

沒有那些循環可能會調整分支預測器(對於合理大小的數組,無論如何)。我期望底層JIT將任何重複數組長度引用從(2)式轉換爲(3)式。在所有情況相同的情況下,(1)緩存性能並不比(2)或(3)更糟糕,因爲它的運行倒退;對於一個給定的數組,相同的緩存行將被加載並且經常被擊中(或未擊中)。

當然,我的期望是無關緊要的。要知道的唯一方法就是測試!當測試時,請記住writing good microbenchmarks is hard

+1

+1 ...編輯我的評論:) –

+1

我可以測試它,但我更願意深入瞭解JVM的功能。我們還假設它是性能至關重要的。 – Nicholas

+0

@Nicholas我覺得你想用C語言編寫代碼,但要求使用Java :) –