Является ли это ошибкой в Scala 2.9.1 ленивой реализации или просто артефактом декомпиляции
Я рассматриваю возможность использования Scala в довольно интенсивной вычислительной программе. Профилирование версии нашего кода на С++ показывает, что мы могли бы извлечь большую пользу из оценки Lazy. Я пробовал это в 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
Ответы
Ответ 1
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) безопасный и очень быстрый двойной блокировки.
Подробнее:
Какова (скрытая) стоимость lazy val Scala?
Имейте в виду, что если у вас есть несколько ленивых членов val в классе, только один из них может быть инициализирован сразу, поскольку они защищены synchronized(this) { ... }
.
Ответ 2
То, что я получаю с 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