問:在什麼情況下,Java 比 C++ 慢很多?
答:Ben Maurer:
為了回答這個問題,需要先將該問題分成幾個可能引起慢的原因:
垃圾回收器。這是一把「雙面刃」。如果你的程式遵循「大部分物件都在年青代中消亡」模型,垃圾回收器是非常有利的(很少的碎片,更好的快取局部性)。但是,如果程式不遵循該模型,JVM將花費大量資源來回收堆記憶體。
大對象。在Java中,所有的物件都有一個vtable指針,而C++中使用POD結構沒有額外開銷。此外,所有的Java物件是可以被鎖定的。其實作依賴於JVM,這可能需要在物件中增加額外的欄位。大物件 == 快取更少的物件 == 更慢。 (另一方面,Java 7 用64位元記錄壓縮後的指針,這也是造成這個問題的一部分原因。
缺乏內聯物件。在Java中,所有的類別都是指針。在C++中,物件可以和其它物件一起分配,或在堆疊上分配。不小的開銷。 ,你只使用String物件(沒有char[]),它將會很慢,因為需要分配額外的空間。嘗試避免虛函數調用,但是很多場景下,JVM無法解決這個問題。從彙編得益的程式碼Java可能表現不佳。間產生鴻溝的原因之一。低效的強制抽象和平台函數也會導致速度下降,但這通常只會因為低階的程式碼才會產生。 Todd Lipcon
我基本上同意Ben Maurer(hey Ben!)的回答。當執行緒逃逸出去的時候,逃逸分析能有效地決定一種固定分配。指針碰撞(bump the pointer)」分配,這等同於C中的棧分配。
譯者註:
逃逸分析 Escape Analysis,是一種編譯最佳化技術,指分析指針動態範圍的方法。通俗地說,當一個物件的指標被多個方法或執行緒引用時,我們稱這個指標發生了逃逸。放在一邊,空閒的內存被放在另一邊,中間放著一個指針作為分界點的指示器,那所分配內存就僅僅是把那個指針向空閒空間那邊挪動一段與對像大小相等的距離,這種分配方式稱為「指標碰撞」。
即使沒有逃逸分析,年青代的分配也是透過指標碰撞方式,在執行緒本地分配緩衝區(TLAB)中完成的,不需要進行同步。所以Java中小物件的分配有的時候比C語言實作的 malloc() 方式更快。更好的 malloc 方法像Google的 tcmalloc,採用了類似的方式。但是由於C語言無法在記憶體中對分配後的物件重新分配,所以某些方面會受到限制。
雖然有內聯和虛函數問題,但是實際上,Java在某些情況下甚至可以做的比C更好。特別是,C不能透過動態連結功能來實現內聯,因為內聯是在編譯時期進行的,而不是運行時期。而Java可越過不同的類別或函式庫的邊界來動態內聯一個函數,即使該類別的真正實作在編譯期間還不可用。在許多工作中,這種方式比C++的虛函數呼叫更有效,C++虛函數呼叫總是需要呼叫虛擬表。而JIT編譯器,如果之前動態屬性已經遺失(如新的類別已經載入),能夠聰明地取消內聯最佳化。
新版本的GCC提供一些這方面優化,稱為“全程序優化”或“鏈接時優化",允許在工程範圍內越過對象文件進行內聯。但是,基本上還是不允許透過動態連結的方式來實現內聯(如透過內聯的方式實現zlib的呼叫等)。許多大型專案都是透過複製標準庫的功能到它們的程式碼中來實現。