11

我正在考慮在一個相當計算密集的程序上使用Scala。剖析我們的代碼的C++版本顯示,我們可以從懶惰評估中獲益很多。我已經在Scala 2.9.1中試過了,並且非常喜歡它。但是,當我通過反編譯器運行該類時,實現看起來不太正確。我假設它的反編譯器的假象,但我想獲得更確鑿的答案...這是Scala 2.9.1中的一個bug懶惰的實現還是隻是一個反編譯的工件

考慮以下簡單的例子:

class TrivialAngle(radians : Double) 
{ 
    lazy val sin = math.sin(radians) 
} 

當我反編譯它,我得到這個:

import scala.ScalaObject; 
import scala.math.package.; 
import scala.reflect.ScalaSignature; 

@ScalaSignature(bytes="omitted") 
public class TrivialAngle 
    implements ScalaObject 
{ 
    private final double radians; 
    private double sin; 
    public volatile int bitmap$0; 

    public double sin() 
    { 
    if ((this.bitmap$0 & 0x1) == 0); 
    synchronized (this) 
    { 
     if (
     (this.bitmap$0 & 0x1) == 0) 
     { 
     this.sin = package..MODULE$.sin(this.radians); 
     this.bitmap$0 |= 1; 
     } 
     return this.sin; 
    } 
    } 

    public TrivialAngle(double radians) 
    { 
    } 
} 

對我而言,返回塊位於錯誤的位置,並且您將始終獲取鎖定。這不可能是真正的代碼在做什麼,但我無法證實這一點。任何人都可以確認或否認我有一個虛假的反編譯,並且這個懶惰的實現是有點合理的(即,只有當它計算值時鎖定,並且不會獲得後續調用的鎖定)。

謝謝!

供參考,這是我使用的反編譯: http://java.decompiler.free.fr/?q=jdgui

+0

計算密集型,你想要做鎖嗎? –

+0

不,我有很多項目,我只想計算,如果/當我需要他們,我想這些結果緩存計算後。根據實施情況,懶惰正是我想要的。如果我可以指定不鎖定,那會更好,但這不是這個問題的關鍵。 – fbl

+1

那麼我已經做了大量的計算密集型C/C++/Fortran代碼調整(製藥仿真)。我使用的方法[是這個](http://stackoverflow.com/questions/375913/what-c​​an-i-use-to-profile-c-code-in-linux/378024#378024)。 (即使講清楚,你也不能總是相信分析器。) –

回答

9

scala -Xprint:jvm揭示了真實的故事:

[[syntax trees at end of jvm]]// Scala source: lazy.scala 
package <empty> { 
    class TrivialAngle extends java.lang.Object with ScalaObject { 
    @volatile protected var bitmap$0: Int = 0; 
    <paramaccessor> private[this] val radians: Double = _; 
    lazy private[this] var sin: Double = _; 
    <stable> <accessor> lazy def sin(): Double = { 
     if (TrivialAngle.this.bitmap$0.&(1).==(0)) 
     { 
      TrivialAngle.this.synchronized({ 
      if (TrivialAngle.this.bitmap$0.&(1).==(0)) 
       { 
       TrivialAngle.this.sin = scala.math.`package`.sin(TrivialAngle.this.radians); 
       TrivialAngle.this.bitmap$0 = TrivialAngle.this.bitmap$0.|(1); 
       () 
       }; 
      scala.runtime.BoxedUnit.UNIT 
      }); 
     () 
     }; 
     TrivialAngle.this.sin 
    }; 
    def this(radians: Double): TrivialAngle = { 
     TrivialAngle.this.radians = radians; 
     TrivialAngle.super.this(); 
    () 
    } 
    } 
} 

這是一個(因爲JVM 1.5)的安全,而且速度非常快,雙重檢查鎖定。

更多細節:

What's the (hidden) cost of Scala's lazy val?

請注意,如果您有多個懶VAL成員的一類,但其中只有一個可以在一次初始化,因爲它們是由synchronized(this) { ... }把守。

+0

謝謝!如果我能將2個回答標記爲「正確」,我會這麼做。我選擇了這個,因爲它稍微更具可讀性;)我完全意識到懶字段一次只能初始化一個事實。這可能會,也可能不會影響我們如何構建我們的解決方案,但它肯定會通知您的決定。 – fbl

9

什麼我得到javap -c不符合您的反編譯。特別是當發現字段被初始化時,沒有監視器輸入。版本2.9.1也是如此。當然,仍然存在着易失性訪問所暗示的內存障礙,所以它不會完全免費。開始///評論是我

public double sin(); 
    Code: 
    0: aload_0 
    1: getfield  #14; //Field bitmap$0:I 
    4: iconst_1 
    5: iand 
    6: iconst_0 
    7: if_icmpne  54 /// if getField & 1 == O goto 54, skip lock 
    10: aload_0 
    11: dup 
    12: astore_1 
    13: monitorenter 
      /// 14 to 52 reasonably equivalent to synchronized block 
      /// in your decompiled code, without the return 
    53: monitorexit 
    54: aload_0 
    55: getfield  #27; //Field sin:D 
    58: dreturn  /// return outside lock 
    59: aload_1  /// (this would be the finally implied by the lock) 
    60: monitorexit 
    61: athrow 
    Exception table: 
    from to target type 
    14 54 59 any 
+0

謝謝!如果我能將2個回答標記爲「正確」,我會這麼做。這個答案和retronym都揭示了懶惰的本質。 – fbl

+0

沒問題,我更喜歡retrony的答案,更多的是我沒有新的這個-Xprint:jvm選項。如果你真的不相信scala,那麼javap可能仍然是最終的裁判,但如果不是這樣的話,它會更好。 –

相關問題