2015-01-09 65 views
5

我的代碼,這兩個虛擬件(讓我們來看看他們都寫在Java和C#中,所有變量本地):與垃圾收集語言一起使用時,哪種代碼的CPU /內存效率更高?

代碼1:

int a; 
int b = 0; 

for (int i = 1; i < 10 ; i++) 
{ 
    a = 10; 
    b += i; 

    // a lot of more code that doesn't involve assigning new values to "a" 
} 

代碼2:

int b = 0; 

for (int i = 1; i < 10 ; i++) 
{ 
    int a = 10; 
    b += i; 

    // a lot of more code that doesn't involve assigning new values to "a" 
} 

乍一看,我會說這兩個代碼都消耗相同數量的內存,但代碼1的CPU效率更高,因爲它只創建和分配變量a一次。 然後我讀了垃圾收集器是非常有效的,以至於代碼2會更有效地存儲內存(和CPU):在循環內部保留變量a使它屬於Gen0,所以它將在變量b之前被垃圾收集。

因此,與垃圾收集語言一起使用時,代碼2效率更高。我對嗎?

+58

這裏沒有垃圾回收。 –

+3

重複分配'a'有什麼意義?還要注意,這兩位有很大不同(第二位在循環外沒有'a'變量,第一位是)。 –

+8

@ T.J.Crowder顯然它只是一個玩具的例子;一般的想法是將變量作用在循環中是否有意義,或者是否應該將它們拉出循環之外(即使不在循環外部使用)作爲優化。 – Servy

回答

38

的幾點:

  • 整數(和其他原語)是從未被分配過的堆。它們直接在線程堆棧上生存,「分配」和「釋放」是指針的簡單移動,並且只發生一次(當函數被輸入時,並且在返回之後),而不管範圍如何。

  • 經常訪問的基元通常存儲在寄存器中,以便速度,而不管範圍如何。

  • 您的情況a(也可能是b以及整個循環)將被「優化」,優化器足夠智能以在變量值發生變化時檢測情況,但永遠不會讀取,並跳過冗餘操作。或者,如果代碼實際上看起來是a,但沒有修改它,那麼它可能會被優化器替換爲常量值「10」,該值將在任何引用a的位置出現。

  • 的新對象(如果你不喜歡的東西String a = new String("foo")例如不是int)是總是在年輕一代分配的,之後他們生存的一些小的藏品只得到轉移到老根。這意味着,對於大多數情況,當一個對象被分配到一個函數內部,並且從不從外部引用時,除非堆結構迫切需要調整,否則它將永遠不會將其傳遞給舊代。

  • 正如在評論中指出的那樣,虛擬機有時可能決定直接在舊版本中分配一個大對象(這對Java也是如此,而不僅僅是.net),所以上面的幾點僅適用於大多數情況,但不是總是。但是,就這個問題而言,這並沒有什麼區別,因爲如果決定在舊有物體中分配一個物體,無論如何都不考慮其初始參考的範圍。

從性能和記憶的角度來看,你的兩個片段是相同的。儘管如此,從可讀性的角度來看,將所有變量聲明在儘可能小的範圍內總是一個好主意。

+0

對於GC的當前.NET實現和大對象將分配在大對象堆上,這是有效的Gen2(最高一代)。所以分配並不總是最年輕的一代。 – CodesInChaos

+0

@CodesInChaos這是一個很好的觀點(對於Java也是如此,而不僅僅是.net)。感謝您指出!我已經更新了答案以反映這一點。 – Dima

+0

@CodesInChaos:不,LOH與Gen2實際上並不相同。 LOH根本不是壓實過程的一部分。 –

18

在片段2中的代碼實際執行之前,它最終會變成看起來像片段1後面的代碼(無論是編譯器還是運行時)。因此,這兩個片段的性能將會相同,因爲它們在某些時候會在功能上編譯相同的代碼。

請注意,對於非常短暫的變量,它們實際上很可能沒有爲它們分配內存。它們可能完全存儲在一個寄存器中,涉及0個內存分配。

+1

編譯器還是jit? – TheLostMind

+1

@TheLostMind是的,在堆棧中的某個地方。爲了這個問題的目的,它應該不太重要。 – Servy

+1

我同意,它是怎麼發生的*無關緊要*並且從分發到分發的變化:) – TheLostMind