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
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) { ... }
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With