2011-02-10 53 views
13

從樓梯一本書的摘錄:斯卡拉的抽象類真的比特性表現更好嗎?

如果效率是非常重要的,對精益使用類。與接口方法調用相比,大多數Java運行時對類成員的虛擬方法調用是更快速的操作 。性狀被編譯到接口 ,因此可能會支付一些性能開銷。但是,只有當您知道該特徵構成性能 瓶頸並且有證據表明使用班級實際上 解決了問題時,您才應該做出此選擇。

我寫了一些簡單的代碼,看看幕後發生了什麼。在抽象類中我注意到了invokevirtual,在接口的情況下我注意到了invokeinterface。 但是無論我寫什麼樣的代碼,他們總是大致執行相同的操作。我在服務器模式下使用HotSpot 1.6.0_18。

JIT是否做了這麼好的優化工作? 有沒有人有一個示例代碼,證明這本書有關invokevirutal是更快的操作索賠?

+0

如果您正在認真考慮這種低級優化,那麼您正在使用錯誤的編程語言。 – Raphael 2011-02-10 21:37:48

+4

@Raphael如果您在Android系統或類似系統上使用Scala,您可能需要注意所有那些您通常不會重複考慮的事情。 – wheaties 2011-02-10 21:54:59

回答

7

如果HotSpot注意到呼叫站點的所有實例都是相同類型的,它可以使用單形方法調用,並且虛擬和接口方法都以相同的方式優化。文檔PerformanceTechniquesVirtualCalls沒有區分虛擬方法和接口方法。

但在一般非單形的情況下可能會有一些差異。該InterfaceCalls文件說:

有在其中每一個實現該接口的類中顯示的固定偏移一個接口的方法沒有簡單的前綴方案。相反,在一般(非單形)情況下,彙編代碼存根例程必須從接收方的klassOop中獲取已實現接口的列表,然後遍歷該列表以查找當前目標接口。

這也證實了單態的情況下是兩個相同的:

幾乎相同優化適用於接口的調用,以虛擬呼叫。與虛擬呼叫一樣,大多數界面調用都是單形的,因此可以通過便宜的支票以直接調用方式呈現。

其他JVM可能有不同的優化。

您可以嘗試一個微型基準測試(if you know how),它調用多個實現相同接口的類的方法,以及多個擴展相同抽象類的類。這樣,應該可以強制JVM使用非單形方法調用。 (雖然在現實生活中可能沒有什麼區別,因爲大多數呼叫站點都是單形的。)

0

Inside the Java Virtual Machine(調用指令和速度)引述:

當Java虛擬機 遇到invokevirtual 指令和解決符號 參照直接引用的 實例方法,即直接引用 很可能是表格中的方法 的偏移量。從這一點開始,可以使用相同的偏移量。對於 invokeinterface指令,然而, 虛擬機將不得不通過方法表 搜索 遇到的指令每次 單的時候,因爲它不能承擔 偏移量是一樣的前面 時間。

3

底線是,你將不得不爲自己的應用程序自己測量它,看它是否重要。您可以通過當前的JVM獲得相當違反直覺的結果。嘗試一下。

文件TraitAbstractPackage.scala

package traitvsabstract 

trait T1 { def x: Int; def inc: Unit } 
trait T2 extends T1 { def x_=(x0: Int): Unit } 
trait T3 extends T2 { def inc { x = x + 1 } } 

abstract class C1 { def x: Int; def inc: Unit } 
abstract class C2 extends C1 { def x_=(x0: Int): Unit } 
abstract class C3 extends C2 { def inc { x = x + 1 } } 

文件TraitVsAbstract.scala

object TraitVsAbstract { 
    import traitvsabstract._ 

    class Ta extends T3 { var x: Int = 0} 
    class Tb extends T3 { 
    private[this] var y: Long = 0 
    def x = y.toInt 
    def x_=(x0: Int) { y = x0 } 
    } 
    class Tc extends T3 { 
    private[this] var xHidden: Int = 0 
    def x = xHidden 
    def x_=(x0: Int) { if (x0 > xHidden) xHidden = x0 } 
    } 

    class Ca extends C3 { var x: Int = 0 } 
    class Cb extends C3 { 
    private[this] var y: Long = 0 
    def x = y.toInt 
    def x_=(x0: Int) { y = x0 } 
    } 
    class Cc extends C3 { 
    private[this] var xHidden: Int = 0 
    def x = xHidden 
    def x_=(x0: Int) { if (x0 > xHidden) xHidden = x0 } 
    } 

    def Tbillion3(t: T3) = { 
    var i=0; while (i<1000000000) { t.inc; i+=1 }; t.x 
    } 

    def Tbillion1(t: T1) = { 
    var i=0; while (i<1000000000) { t.inc; i+=1 }; t.x 
    } 

    def Cbillion3(c: C3) = { 
    var i=0; while (i<1000000000) { c.inc; i+=1 }; c.x 
    } 

    def Cbillion1(c: C1) = { 
    var i=0; while (i<1000000000) { c.inc; i+=1 }; c.x 
    } 

    def ptime(f: => Int) { 
    val t0 = System.nanoTime 
    val ans = f.toString 
    val t1 = System.nanoTime 
    printf("Answer: %s; elapsed: %.2f seconds\n",ans,(t1-t0)*1e-9) 
    } 

    def main(args: Array[String]) { 
    for (i <- 1 to 3) { 
     println("Iteration "+i) 
     val t1s,t3s = List(new Ta, new Tb, new Tc) 
     val c1s,c3s = List(new Ca, new Cb, new Cc) 
     t1s.foreach(x => ptime(Tbillion1(x))) 
     t3s.foreach(x => ptime(Tbillion3(x))) 
     c1s.foreach(x => ptime(Cbillion1(x))) 
     c3s.foreach(x => ptime(Cbillion3(x))) 
     println 
    } 
    } 
} 

每個人都應該打印出10億的答案,而且所花費的時間應該是零(如果JVM是很聰明)或約需要添加十億個數字。但至少在我的系統中,Sun JVM向後優化 - 重複運行速度變慢 - 而抽象類比特性慢。 (你可能想用java -XX:+PrintCompilation來試圖弄清楚出了什麼問題;我懷疑是殭屍)。

另外,值得注意的是scalac -optimise沒有任何改進的地方 - 這一切都取決於JVM。

相比之下,JRockit JVM的性能始終如一,但同樣,特質擊敗了類。由於時間是一致的,我會報告他們:3.35s的類(3.62s爲一個if語句)與2.51秒的所有特性,if語句或否。 (我發現這種趨勢總體上是真實的:在某些情況下,熱點產生快速的性能,而在其他情況下(例如這種情況),會變得困惑並且非常慢; JRockit從不超速 - 不要打擾試圖從原語中獲得類似C的性能 - 但它很少出現錯誤)。