这是 Scala 2.9.1 延迟实现中的错误还是只是反编译的产物

发布于 2024-12-11 12:28:50 字数 1196 浏览 0 评论 0原文

我正在考虑在一个计算量相当大的程序上使用 Scala。对代码的 C++ 版本进行分析表明,我们可以从惰性求值中获益匪浅。我已经在 Scala 2.9.1 中尝试过并且非常喜欢它。然而,当我通过反编译器运行该类时,实现看起来不太正确。我假设它是反编译器的一个工件,但我想得到一个更结论性的答案...

考虑以下简单的例子:

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

I am considering using Scala on a pretty computationally intensive program. Profiling the C++ version of our code reveals that we could benefit significantly from Lazy evaluation. I have tried it out in Scala 2.9.1 and really like it. However, when I ran the class through a decompiler the implemenation didn't look quite right. I'm assuming that it's an artifact of the decompiler, but I wanted to get a more conclusive answer...

consider the following trivial example:

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

when I decompile it, I get this:

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)
  {
  }
}

To me, the return block is in the wrong spot, and you will always acquire the lock. This can't be what the real code is doing, but I am unable to confirm this. Can anyone confirm or deny that I have a bogus decompilation, and that the lazy implementation is somewhat reasonable (ie, only locks when it is computing the value, and doesn't acquire the lock for subsequent calls?)

Thanks!

For reference, this is the decompiler I used:
http://java.decompiler.free.fr/?q=jdgui

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

筑梦 2024-12-18 12:28:50

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 起)安全且非常快速的双重检查锁。

更多详细信息:

Scala 的惰性 val 的(隐藏)成本是多少?

请注意,如果类中有多个惰性 val 成员,则一次只能初始化其中一个,因为它们由 synchronized(this) { ... } 保护。

scala -Xprint:jvm reveals the true story:

[[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();
      ()
    }
  }
}

It's a (since JVM 1.5) safe, and very fast, double checked lock.

More details:

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

Be aware that if you have multiple lazy val members in a class, only one of them can be initialized at once, as they are guarded by synchronized(this) { ... }.

愿与i 2024-12-18 12:28:50

我用 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

What I get with javap -c does not correspond to your decompile. In particular, there is no monitor enter when the field is found to be initialized. Version 2.9.1 too. There is still the memory barrier implied by the volatile access of course, so it does not come completely free. Comments starting with /// are mine

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文