Это ошибка в ленивой реализации Scala 2.9.1 или просто артефакт декомпиляции - PullRequest
11 голосов
/ 23 октября 2011

Я рассматриваю возможность использования 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

Ответы [ 2 ]

9 голосов
/ 23 октября 2011

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) безопасный и очень быстрый замок с двойной проверкой.

Подробнее:

Какова (скрытая) стоимость ленивого val в Scala?

Имейте в виду, что если в классе несколько ленивых членов val, только один из них может быть инициализирован одновременно, так какони охраняются synchronized(this) { ... }.

9 голосов
/ 23 октября 2011

То, что я получаю с 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
...