2013-10-31 53 views
9

我有一個關於風格一段代碼備註:是反覆實例化一個匿名類浪費?

Iterable<String> upperCaseNames = Iterables.transform(
    lowerCaseNames, new Function<String, String>() { 
     public String apply(String input) { 
      return input.toUpperCase(); 
     } 
    }); 

的人說,我每次去通過這個代碼的時候,我實例化這個匿名函數類,而我倒是應該有一個單一的例如,一個靜態變量:

static Function<String, String> toUpperCaseFn = 
    new Function<String, String>() { 
     public String apply(String input) { 
      return input.toUpperCase(); 
     } 
    }; 
... 
Iterable<String> upperCaseNames = 
    Iterables.transform(lowerCaseNames, toUpperCaseFn); 

在一個非常膚淺的層面上,這有點合理;多次實例化一個類需要浪費內存或者其他東西,對吧?另一方面,人們在代碼的中間實例化匿名類,就像沒有明天一樣,編譯器將它優化掉是微不足道的。

這是一個有效的擔憂?

+1

Java中的對象分配速度非常快。實例化一個匿名類與「常規」類沒有什麼不同。由於匿名類沒有任何實例變量,幾乎沒有使用任何內存(並且對象本身可能會被分配到堆棧上 - 但我不確定)。但是那個人基本上是正確的:在一個循環中實例化對象*會很貴。但是這與匿名類無關,對於實例化任何類都是如此。 –

+0

由於「不必要地創建一次性類」,我「浪費」了很多CPU週期。然後,我再次爲桌面/服務器編寫代碼,*我不在乎是否可以節省一毫秒或兩個[總體] *。相反,編寫代碼,以便很容易推斷 - 哪些*可能*(或可能不)意味着「重複使用」實例和/或給它一個綽號 - 然後進行配置。沒有配置文件的問題?沒問題! – user2864740

+0

浪費是浪費。它是一個匿名類與它無關。 – EJP

回答

7

有關Sun/Oracle的JVM優化有趣的事實,如果實例未在線程之外傳遞的對象,JVM將創建堆棧中的對象而不是堆。

通常,堆棧分配與暴露內存模型的語言(如C++)相關聯。您不必在C++中使用delete堆棧變量,因爲它們在範圍退出時自動解除分配。這與堆分配相反,這需要您在完成該操作時刪除指針。

在Sun/Oracle JVM中,分析字節碼以確定對象是否可以「逃脫」線程。有three levels of escape

  1. 無轉義 - 該對象只用於它創建的方法/範圍內,並且該對象不能在線程之外訪問。
  2. Local/Arg轉義 - 創建它的方法或傳遞給它所調用的方法的方法返回該對象,但當前堆棧跟蹤中的方法都不會將該對象放置在可以在線。
  3. 全局轉義 - 該對象放置在可以在另一個線程中訪問的地方。

這基本上類似於這些問題,1)我是否通過/返回它,2)我是否將它與附加到GC根的東西相關聯?在您的具體情況下,匿名對象將被標記爲「本地轉義」,並且將被分配到堆棧,然後在for循環的每次迭代中簡單地彈出堆棧以進行清理,因此清理它將是超級快。對不起,當我寫下我的答案時,我並沒有太在意。它實際上是本地轉義,這隻意味着對象上的任何鎖(讀取:使用​​)都將被優化。 (爲什麼要在另一個線程中永遠不會使用的東西上進行同步?)這與「不可逃避」不同,其中做在堆棧上分配。需要注意的是,這個「分配」與堆分配不同。它真正做的是在堆棧中爲非轉義對象內的所有變量分配空間。如果在禁止轉義對象內有3個字段,即int,StringMyObject,則將分配三個堆棧變量:intString引用和MyObject引用。然後將對象分配優化掉,構造函數/方法將使用本地堆棧變量而不是堆變量運行。

這就是說,這聽起來像是我過早的優化。除非後來的代碼被證明是慢的並且導致性能問題,否則不應該做任何事情來降低其可讀性。對我來說,這段代碼非常可讀,我會放棄它。當然,這完全是主觀的,但「性能」不是改變代碼的好理由,除非它與算法運行時間有關。通常,不成熟的優化是中級編碼器的指標。 「專家」(如果有這樣的事情)只需編寫易於維護的代碼即可。

+0

嗯。您發佈的有關轉義級別的鏈接具有以下文本:「在轉義分析之後,服務器編譯器**消除了標量可替換對象分配**以及來自生成代碼的關聯鎖。服務器編譯器還消除了所有非全局轉義對象的鎖。它不會替代堆棧分配和堆棧分配**對於非全局轉義對象。「 – user711413

+0

@ user711413把它放在一起匆忙中,並沒有清楚地思考,對不起,我會更新我的答案 – Brian

+1

實際上,在接收該函數的代碼內聯後,函數對象很可能會變成「No Escape」狀態(取決於該方法的作用),但這並不意味着會有堆棧分配 - 因爲該對象不包含任何狀態,根本沒有分配,相反,函數的邏輯會被內聯到被調用方法的代碼中(在這個上下文中),但是當用lambda表達式代替時,你會得到一個單例,即使因此Java 8讓我們無需考慮這些事情 – Holger

1

簡答:不 - 別擔心。

長答案:它取決於你實例化它的頻率。如果在一個經常被稱爲緊密循環的環境中,也許 - 但請注意,當應用該函數時,它會在Iterable中爲每個項目調用String.toUpperCase()一次 - 每個調用都可能會創建一個新的String,這將創建更多的GC流失。

「過早的優化是所有罪惡的根源」 - 克努特

1

發現這個線程:Java anonymous class efficiency implications,你會發現很有趣

做了一些微小的標杆。微基準是一個比較:在每個循環迭代中實例化一個(靜態內部)類,實例化一個(靜態內部)類並在循環中使用它,以及兩個類似的但帶有匿名類的類。對於微型基準測試來說,編譯器似乎將匿名類從循環中提取出來並按照預期將匿名類提升爲調用者的內部類。這意味着所有四種方法在速度上難以區分。我還將它與外面的課程相比較,同樣的速度。一個匿名類可能需要約128位的空間更多

你可以看看我的微基準測試http://jdmaguire.ca/Code/Comparing.java & http://jdmaguire.ca/Code/OutsideComp.java。我在wordLen,sortTimes和listLen的各種值上運行這個。而且,JVM預熱緩慢,所以我打亂了方法調用。請不要評論我可怕的非評論代碼。我的計劃比RL中的要好。 Microbenching標記幾乎和過早優化一樣邪惡和無用。