問:在什麼情況下,Java 比 C++ 慢很多?
答:Ben Maurer:
為了回答這個問題,需要先將該問題分成幾個可能引起慢的原因:
垃圾回收器。這是一把“雙刃劍”。如果你的程序遵循“大部分對象都在年青代中消亡”模型,垃圾回收器是非常有利的很少的碎片,更好的緩存局部性)。但是,如果程序不遵循該模型,JVM將花費很多資源來回收堆內存。
大對象。在Java中,所有的對象都有一個vtable指針,而C++中使用POD結構沒有額外開銷。此外,所有的Java對象是可以被鎖定的。其 實現依賴於JVM,這可能需要在對象中增加額外的字段。大對象 == 緩存更少的對象 == 更慢。另一方面,Java 7 用64位記錄壓縮後的指針,這也是造成該問題的一部分原因。
缺乏內聯對象。在Java中,所有的類都是指針。在C++中,對象可以和其它對象一起分配,或者在棧上分配。這樣可以提高緩存的局部性,從而減少動態內存分配的開銷。
平台函數調用。在Java中,JNI的調用或者將對象編譯成本地代碼都會帶來不小的開銷。如果你需要頻繁調用客戶端的C++代碼,會增加很大的開銷。
低效的強制抽象。例如,在Java中字符串是不可變的。如果你想寫一個XML分析器,你只使用String對象沒有char[]),它將會很慢,因為需要分配額外的空間。
虛函數調用增加。JVM中,幾乎所有的函數調用都是虛函數調用。有許多代碼嘗試避免虛函數調用,但是很多場景下,JVM無法解決這個問題。這阻礙了代碼的內聯,使代碼變慢。
缺乏高級的編譯特征及轉為匯編的能力。 如果你寫了一段能從匯編得益的代碼Java可能表現不佳。
在我看來,最大的問題是垃圾回收。在程序中,強制在大的內存中進行多次完全GC,是最容易導致Java和C++之間產生鴻溝的原因之一。除此之外,如果將程序的工作集放在L2緩存之外,像大對象、缺乏內聯對象等問題,也會導致兩者之間的巨大差別。
低效的強制抽象和平台函數也會導致速度下降,但是這通常只會因為低級的代碼才會產生。如果你使用寫得很好的Java代碼庫,這通常不是什麼大問題。
答:Todd Lipcon
我基本同意Ben Maurerhey Ben!)的回答。有幾個小點不同:
在最新的JVM中,當這種分配永遠不會從a)局部函數或b)局部線程逃逸出去的時候,逃逸分析能有效地決定一種固定分配。也就是說當分配不需要 加鎖,通常是在自身的棧空間上進行的。這兩種情況下都是一種簡單的“指針碰撞bump the pointer)”分配,這等同於C中的棧分配。
譯者注:
即使沒有逃逸分析,年青代的分配也是通過指針碰撞方式,在線程本地分配緩沖區TLAB)中完成的,不需要進行同步。所以Java中小對象的分配有 的時候比C語言實現的 malloc() 方式更快。更好的 malloc 方法像Google的 tcmalloc,采用了類似的方式。但是由於C語言無法在內存中對分配後的對象重新分配,所以某些方面會受到限制。
雖然存在內聯和虛函數問題,但是實際上,Java在某些情況下甚至可以做的比C更好。特別是,C不能通過動態鏈接功能來實現內聯,因為內聯是在編譯 時期進行的,而不是運行時期。而Java可越過不同的類或庫的邊界來動態內聯一個函數,即使該類的真正實現在編譯期間還不可用。許多工作中,這種方式比 C++的虛函數調用更有效,C++虛函數調用總是需要調用虛表。而JIT編譯器,如果之前動態屬性已經丟失如新的類已經被加載),能夠聰明地取消內聯優 化。
新版本的GCC提供一些這方面優化,稱為“全程序優化”或“鏈接時優化”http://gcc.gnu.org/wiki/LinkTime…),允許在工程范圍內越過對象文件進行內聯。但是,基本上還是不允許通過動態鏈接的方式來實現內聯如通過內聯的方式實現zlib的調用等)。許多大型項目都是通過復制標准庫的功能到它們的代碼中來實現。