2014-08-30 52 views
3

我最初想要測試一些與Java中浮點性能優化不同的東西,即除法除以5.0f和乘以0.2f(乘法似乎在沒有預熱的情況下比較慢,但以大約1.5倍分別)。爲什麼在預熱階段浮點運算更快?

研究結果後,我注意到我忘了添加一個熱身階段,正如經常進行性能優化時所建議的那樣,所以我添加了它。而且,令我非常吃驚的是,在多次測試中,結果平均快了25倍。與預熱階段

Divide by 5.0f: 382224 
Multiply with 0.2f: 490765 

結果

我用下面的代碼測試了它:

public static void main(String args[]) 
{ 
    float[] test = new float[10000]; 
    float[] test_copy; 

    //warmup 
    for (int i = 0; i < 1000; i++) 
    { 
     fillRandom(test); 

     test_copy = test.clone(); 

     divideByTwo(test); 
     multiplyWithOneHalf(test_copy); 
    } 

    long divisionTime = 0L; 
    long multiplicationTime = 0L; 

    for (int i = 0; i < 1000; i++) 
    { 
     fillRandom(test); 

     test_copy = test.clone(); 

     divisionTime += divideByTwo(test); 
     multiplicationTime += multiplyWithOneHalf(test_copy); 
    } 

    System.out.println("Divide by 5.0f: " + divisionTime); 
    System.out.println("Multiply with 0.2f: " + multiplicationTime); 
} 

public static long divideByTwo(float[] data) 
{ 
    long before = System.nanoTime(); 

    for (float f : data) 
    { 
     f /= 5.0f; 
    } 

    return System.nanoTime() - before; 
} 

public static long multiplyWithOneHalf(float[] data) 
{ 
    long before = System.nanoTime(); 

    for (float f : data) 
    { 
     f *= 0.2f; 
    } 

    return System.nanoTime() - before; 
} 

public static void fillRandom(float[] data) 
{ 
    Random random = new Random(); 

    for (float f : data) 
    { 
     f = random.nextInt() * random.nextFloat(); 
    } 
} 

結果而不預熱階段

Divide by 5.0f: 22081 Multiply with 0.2f: 10885 

我無法解釋的另一個有趣的變化是什麼操作更快的轉向(分割與乘法)。如前所述,沒有熱身賽,分區似乎有點快,而在熱身之後,似乎要慢兩倍。

我嘗試添加一個初始化塊,將值設置爲隨機值,但它並不影響結果,也沒有添加多個預熱階段。方法操作的數字是相同的,所以不能成爲原因。

這種行爲的原因是什麼?這是什麼熱身階段,它是如何影響性能的,爲什麼在預熱階段操作速度更快,爲什麼操作速度更快呢?

回答

12

熱身之前Java將通過解釋器運行字節代碼,請考慮如何編寫可以在java中執行java字節代碼的程序。熱身後,熱點將爲您正在運行的CPU生成本地彙編器;利用該cpus功能集。這兩者之間有着顯着的性能差異,解釋器將爲單字節代碼運行許多cpu指令,而熱點生成本地彙編代碼就像編譯C代碼時gcc所做的那樣。這是劃分時間和乘法時間之間的差異最終會落到正在運行的CPU上,而且它將只是一個cpu指令。

難題的第二部分是熱點還記錄統計數據,這些統計數據測量代碼的運行時行爲,當它決定優化代碼時,它將使用這些統計數據執行優化,這些優化在編譯時不一定可行。例如,它可以降低空檢查,分支錯誤預測和多態方法調用的成本。

總之,必須放棄預熱的結果。

Brian Goetz在這個問題上寫了一篇很好的文章here

========

附:什麼「JVM熱身」是指概述

JVM「熱身」是一個鬆散的短語,並且不再嚴格地說單JVM的階段或階段。人們傾向於用它來指JVM字節碼編譯爲本地字節碼後JVM性能穩定的想法。事實上,當一個人開始在表面下劃傷並深入研究JVM內部時,很難讓Hotspot爲我們做的事情留下深刻的印象。我的目標只是讓你更好地感受Hotspot能夠以表演的名義所做的更多細節,我推薦閱讀Brian Goetz,Doug Lea,John Rose,Cliff Click和Gil Tene等人的文章。

如前所述,JVM通過運行Java的解釋器啓動。儘管嚴格來說不是100%正確的,但可以將解釋器看作是一個大的開關語句和一個遍歷每個JVM字節碼(命令)的循環。 switch語句中的每種情況都是JVM字節碼,如將兩個值一起添加,調用方法,調用構造函數等等。迭代的開銷和跳過命令非常大。因此執行單個命令通常會使用10倍以上的彙編命令,這意味着速度要慢10倍以上,因爲硬件必須執行如此多的命令和緩存會受到該解釋器代碼的污染,理想情況下,我們更願意將注意力集中在我們的實際程序上。回想一下Java的早期時代,Java贏得了非常緩慢的聲譽;這是因爲它最初只是一種完全解釋的語言。

後來JIT編譯器被添加到Java中,這些編譯器會在調用方法之前將Java方法編譯爲本地CPU指令。這消除了解釋器的所有開銷,並允許以硬件執行代碼的執行。雖然硬件內部執行速度更快,但這種額外的編譯在啓動Java時創建了一個停頓。這部分是'暖身階段'的術語所佔據的地方。

向JVM引入Hotspot是一個改變遊戲規則的遊戲。現在JVM的啓動速度會更快,因爲它將開始運行帶有解釋器的Java程序,並且單個Java方法將在後臺線程中編譯並在執行期間實時交換出去。本地代碼的生成也可以通過不同級別的優化來完成,有時使用嚴格不正確的非常積極的優化,然後在必要時進行非優化和重新優化以確保正確的行爲。例如,類層次結構意味着花費很大的代價來確定將調用哪個方法,因爲熱點必須搜索層次結構並找到目標方法。在這裏熱點可以變得非常聰明,如果它注意到只有一個類已經被加載,那麼它可以假設將總是如此,並優化和內聯這樣的方法。如果另一個類被加載,現在告訴Hotspot實際上兩個方法之間會做出決定,那麼它將刪除它以前的假設並在飛行中重新編譯。在不同情況下可以做出的優化的完整列表非常令人印象深刻,並且不斷變化。熱點能夠記錄有關運行環境的信息和統計信息,以及當前正在經歷的工作負載,使得優化過程非常靈活和動態。事實上,在單個Java進程的整個生命週期內,很可能隨着工作負載的變化,該程序的代碼將重新生成很多次。可以說Hotspot比傳統的靜態編譯具有更大的優勢,這也是很大程度上爲什麼很多Java代碼可以被認爲與編寫C代碼一樣快的原因。這也使得理解微基準變得更加困難。實際上它讓Oracle的維護人員對JVM代碼本身的理解,處理和診斷問題變得更加困難。花一分鐘時間向這些人提供一個品脫點,Hotspot和JVM作爲一個整體是一個奇妙的工程勝利,在人們說它無法完成的時候,它就脫穎而出。值得記住的是,由於十年左右它是一個相當複雜的野獸;)

因此,鑑於上下文,總結我們指的是在microbenchmarks中預熱一個JVM,使其運行超過10k次的目標代碼並拋出結果使JVM有機會收集統計數據並優化代碼的「熱區」。 10k是一個幻數,因爲Server Hotspot實現在開始考慮優化之前等待許多方法調用或循環迭代。我還會建議在覈心測試運行之間進行方法調用,因爲雖然熱點可以「在堆棧替換」(OSR)上執行,但它在實際應用中並不常見,並且與交換方法的整個實現的行爲不完全相同。

+0

你能否詳細說明一下暖機階段如何影響它,以及它是如何工作的?我還沒有完全理解它,在其他地方似乎沒有太多的解釋。 – 1337 2014-08-31 09:41:34

+0

@ 1337這不是一個快速回答的問題,但我在問題末尾添加了一個部分以提供更多詳細信息。沒有一個簡單的答案,因爲「熱身」實際上是一個用來覆蓋JVM許多方面的鬆散術語,但我會放棄它。 – 2014-08-31 18:27:44

+0

令人驚歎。非常感謝。我會爲你額外提供50點聲望賞金,但我認爲我必須再等一天才能投入獎金:) – 1337 2014-08-31 18:56:43

4

你沒有測量任何有用的「沒有預熱階段」;您正在測量解釋代碼的速度,以及生成堆棧替換需要多長時間。也許分裂會導致更早的編制。

有許多指導方針和各種軟件包可用於構建不受此類問題影響的微基準標記。如果你打算繼續做這種事情,我建議你閱讀指南並使用現成的軟件包。

+0

儘管這確實回答了我的問題的第一部分,但它仍未回答第二部分:爲什麼輪到哪個操作更快? – 1337 2014-08-30 14:24:47

+2

它非常回答你的問題的一部分。分割碼可能比乘法碼快得多。 – tmyklebu 2014-08-30 14:25:26

+2

@ 1337克里斯回答說,因爲它是由於熱點jvm。 – 2014-08-30 14:25:49