1. 程式人生 > >從三個語言(C++,Java,C#)的幾個效能測試案例來看效能優化

從三個語言(C++,Java,C#)的幾個效能測試案例來看效能優化

       隨著時間的發展,現在的虛擬機器技術越來越成熟了,在有些情況下,Java,.Net等虛擬機器密集計算的效能已經和C++相仿,在個別情況下,甚至還要更加優秀。本文詳細分析幾個效能測試案例,探討現象背後的原因。

       來看兩個簡單的測試用例。如下圖所示,均是迴圈5000次,操作 len = 1000000 的連續記憶體,計算執行時間。左側為test1,右側為test2。

       類似的程式在 .net core 3.0 Preview6下測試。

       測試結果對比如下:

       我們可以看見,對於test1,C++版本要快很多,對於test2,C#版本和C++版本效能相當,甚至略快。

       為什麼會出現這種現象呢?下面來具體分析:

       test1 的迴圈的賦值是位置無關的,因此,編譯器可以通過SIMD等平行計算指令來優化,test2 的迴圈的賦值是位置相關的,編譯器很難使用SIMD等平行計算指令來優化。通過上面的結果可以猜測,VC編譯器,對test1進行了並行優化,而.net core 3.0 preview6 沒有對test1 進行並行優化。

       我們來驗證這一猜測。.net core 3.0 提供了對SIMD 指令的支援,下面手動對test1進行並行優化,測試效能:

 

       結果是0.633s,接近於C++版本的0.441s。相對於優化前的2.289s,提速了3倍多。

       同樣的程式,我用 java 8 測試,結果大吃一驚:

 

       test1 耗時 0.654s,和並行優化後的.net core近似,可見 jvm 虛擬機器對此進行了並行優化。test2 耗時1.755s,比C++版本和.net core版本都要快,並且差距巨大!

 

       顯然,jvm對test2這種情況進行了特殊關照。要理解這一現象,就需要對Java虛擬機器的機制有深入瞭解。HotSpot 虛擬機器裡內建了兩個JIT編譯器:Client Compiler和Server Compiler,簡稱為C1編譯器和C2編譯器。C1編譯器將位元組碼編譯為原生代碼,進行簡單、 可靠的優化,如有必要將加入效能監控的邏輯。C2編譯會啟用一些編譯耗時較長的優化,甚至進行一些激進優化。

       查詢文獻可知,預設情況下,當方法呼叫次數+迴圈回邊次數超過10000、計數器是int等幾個簡單型別、步增是常量時,會觸發C2編譯優化。test2恰恰滿足這三種情況!

       下面我們再設計一個實驗,將步增改為變數,看看測試結果:

       由測試可知,將步增改為變數後,測試結果為6.163秒,和C++及 .net core 測試結果近似。

       針對這個測試案例,可以猜測 C2 優化時進行了迴圈展開。下面,我們在 .net core 下手動展開迴圈,測試效能,驗證我們的猜想:

 

       測試結果為1.983s,近似java8的1.755s。猜想得到驗證。

 ----

       總結:隨著JVM、.Net等虛擬機器技術的發展,語言特性對高效能運算效能影響越來越低,對計算機體系結構、編譯原理、虛擬機器編譯機制的理解,對效能的影響變得更為重要。JVM的自動優化做的非常的強悍,.net core 在這方面還有不小差距,不過 .net core 可以通過手工優化來彌補這一差