代码之家  ›  专栏  ›  技术社区  ›  Jesper

Scala的偷懒成本是多少?

  •  153
  • Jesper  · 技术社区  · 14 年前

    Scala的一个方便的特性是 lazy val ,其中 val 延迟到必要时(第一次访问)。

    当然,一个 懒惰的val 必须有一些开销—Scala必须跟踪值是否已经被计算过,并且计算必须同步,因为多个线程可能会尝试第一次同时访问该值。

    一次旅行的费用到底是多少 懒惰的val 懒惰的val 为了跟踪它是否被评估过,什么是同步的,还有更多的成本吗?

    class Something {
        lazy val (x, y) = { ... }
    }
    

    懒惰的val x y 或者我只得到一次开销,对一对 (x, y)

    6 回复  |  直到 11 年前
        1
  •  86
  •   vergenzt    10 年前

    这是从 scala mailing list 并给出了具体的实现方法 lazy 就Java代码(而不是字节码)而言:

    class LazyTest {
      lazy val msg = "Lazy"
    }
    

    编译为与以下Java代码等效的内容:

    class LazyTest {
      public int bitmap$0;
      private String msg;
    
      public String msg() {
        if ((bitmap$0 & 1) == 0) {
            synchronized (this) {
                if ((bitmap$0 & 1) == 0) {
                    synchronized (this) {
                        msg = "Lazy";
                    }
                }
                bitmap$0 = bitmap$0 | 1;
            }
        }
        return msg;
      }
    
    }
    
        2
  •  39
  •   Mitch Blevins    14 年前

    编译器似乎安排了一个类级别的bitmap int字段,以将多个惰性字段标记为已初始化(或未初始化),并在一个同步块中初始化目标字段(如果位图的相关xor指示有必要)。

    使用:

    class Something {
      lazy val foo = getFoo
      def getFoo = "foo!"
    }
    

     0  aload_0 [this]
     1  getfield blevins.example.Something.bitmap$0 : int [15]
     4  iconst_1
     5  iand
     6  iconst_0
     7  if_icmpne 48
    10  aload_0 [this]
    11  dup
    12  astore_1
    13  monitorenter
    14  aload_0 [this]
    15  getfield blevins.example.Something.bitmap$0 : int [15]
    18  iconst_1
    19  iand
    20  iconst_0
    21  if_icmpne 42
    24  aload_0 [this]
    25  aload_0 [this]
    26  invokevirtual blevins.example.Something.getFoo() : java.lang.String [18]
    29  putfield blevins.example.Something.foo : java.lang.String [20]
    32  aload_0 [this]
    33  aload_0 [this]
    34  getfield blevins.example.Something.bitmap$0 : int [15]
    37  iconst_1
    38  ior
    39  putfield blevins.example.Something.bitmap$0 : int [15]
    42  getstatic scala.runtime.BoxedUnit.UNIT : scala.runtime.BoxedUnit [26]
    45  pop
    46  aload_1
    47  monitorexit
    48  aload_0 [this]
    49  getfield blevins.example.Something.foo : java.lang.String [20]
    52  areturn
    53  aload_1
    54  monitorexit
    55  athrow
    

    以元组形式初始化的值,如 lazy val (x,y) = { ... } x , y ,和 x$1 类型字段 Tuple2

        3
  •  26
  •   Rafael Winterhalter    10 年前

    class Example {
      lazy val x = "Value";
    }
    

    编译为类似以下Java代码的字节代码:

    public class Example {
    
      private String x;
      private volatile boolean bitmap$0;
    
      public String x() {
        if(this.bitmap$0 == true) {
          return this.x;
        } else {
          return x$lzycompute();
        }
      }
    
      private String x$lzycompute() {
        synchronized(this) {
          if(this.bitmap$0 != true) {
            this.x = "Value";
            this.bitmap$0 = true;
          }
          return this.x;
        }
      }
    }
    

    请注意,位图由 boolean . 如果您添加另一个字段,编译器将增加字段的大小,使其能够表示至少2个值,即作为 byte

    但你可能想知道为什么会这样?在进入同步块时,必须清除线程本地缓存,以便非易失性 x 值被刷新到内存中。这篇博客文章 an explanation .

        4
  •  11
  •   Leif Wickland    11 年前

    Scala SIP-20 提出了一种新的lazy-val的实现方法,该方法比“当前”版本更正确,但速度慢了25%。

    这个 proposed implementation 看起来像:

    class LazyCellBase { // in a Java file - we need a public bitmap_0
      public static AtomicIntegerFieldUpdater<LazyCellBase> arfu_0 =
        AtomicIntegerFieldUpdater.newUpdater(LazyCellBase.class, "bitmap_0");
      public volatile int bitmap_0 = 0;
    }
    final class LazyCell extends LazyCellBase {
      import LazyCellBase._
      var value_0: Int = _
      @tailrec final def value(): Int = (arfu_0.get(this): @switch) match {
        case 0 =>
          if (arfu_0.compareAndSet(this, 0, 1)) {
            val result = 0
            value_0 = result
            @tailrec def complete(): Unit = (arfu_0.get(this): @switch) match {
              case 1 =>
                if (!arfu_0.compareAndSet(this, 1, 3)) complete()
              case 2 =>
                if (arfu_0.compareAndSet(this, 2, 3)) {
                  synchronized { notifyAll() }
                } else complete()
            }
            complete()
            result
          } else value()
        case 1 =>
          arfu_0.compareAndSet(this, 1, 2)
          synchronized {
            while (arfu_0.get(this) != 3) wait()
          }
          value_0
        case 2 =>
          synchronized {
            while (arfu_0.get(this) != 3) wait()
          }
          value_0
        case 3 => value_0
      }
    }
    

    截至2013年6月,该SIP尚未获得批准。根据邮件列表的讨论,我希望它可能会被批准并包含在Scala的未来版本中。因此,我认为你应该注意 Daniel Spiewak's observation :

        5
  •  11
  •   Roman    8 年前

    关于这个问题,我写了一篇文章 https://dzone.com/articles/cost-laziness

        6
  •  -6
  •   Huy Le    12 年前

    给定scala为lazy生成的字节码,它可能会遇到双重检查锁定中提到的线程安全问题 http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1