Jaki jest (ukryty) koszt lazy val Scali?

Jedną z przydatnych funkcji Scali Jest lazy val, Gdzie ocena {[2] } jest opóźniona, dopóki nie jest to konieczne (przy pierwszym dostępie).

Oczywiście, lazy val musi mieć jakieś narzuty - gdzieś Scala musi śledzić, czy wartość została już oceniona i ocena musi być zsynchronizowana, ponieważ wiele wątków może próbować uzyskać dostęp do wartości po raz pierwszy w tym samym czasie.

Jaki jest dokładnie koszt lazy val - Czy istnieje ukryta flaga logiczna powiązane z lazy val, aby śledzić, czy został oceniony, czy nie, co dokładnie jest zsynchronizowane i czy są jakieś dodatkowe koszty?

Ponadto Załóżmy, że robię to:

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

Czy to to samo, co posiadanie dwóch oddzielnych lazy val s x i y czy tylko raz, dla pary (x, y)?

Author: Peter Mortensen, 2010-06-14

6 answers

Jest to zaczerpnięte z listy dyskusyjnej scala i podaje szczegóły implementacji lazy w kategoriach kodu Javy (a nie kodu bajtowego):

class LazyTest {
  lazy val msg = "Lazy"
}

Jest skompilowane do czegoś równoważnego Następującemu kodowi Javy:

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;
  }

}
 86
Author: oxbow_lakes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-20 20:33:23

Wygląda na to, że kompilator organizuje pole int mapy bitowej na poziomie klasy, aby oznaczyć wiele leniwych pól jako zainicjowane (lub nie) i inicjalizuje pole docelowe w zsynchronizowanym bloku, jeśli odpowiedni xor mapy bitowej wskazuje, że jest to konieczne.

Użycie:

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

Wyświetla przykładowy kod bajtowy:

 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

Wartości inicjowane krotkami takimi jak lazy val (x,y) = { ... } mają zagnieżdżone buforowanie za pomocą tego samego mechanizmu. Wynik krotki jest leniwie oceniany i buforowany, a dostęp X lub y będzie Uruchom ocenę krotki. Ekstrakcja indywidualnej wartości z krotki jest wykonywana niezależnie i leniwie (i buforowana). Tak więc powyższy kod podwójnej instancji generuje x, y, oraz pole x$1 typu Tuple2.

 39
Author: Mitch Blevins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-14 23:22:38

W Scali 2.10, wartość leniwa jak:

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

Jest skompilowany do kodu bajtowego, który przypomina następujący kod 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;
    }
  }
}

Zwróć uwagę, że bitmapa jest reprezentowana przez boolean. Jeśli dodasz kolejne pole, kompilator zwiększy rozmiar pola, aby móc reprezentować co najmniej 2 wartości, np. jako byte. To się dzieje na wielkich zajęciach.

Ale możesz się zastanawiać, dlaczego to działa? Pamięć podręczna thread-local musi zostać wyczyszczona podczas wprowadzania zsynchronizowanego bloku, tak aby wartość nieulotna x jest zapisywana w pamięci. Ten artykuł na blogu daje Wyjaśnienie.
 26
Author: Rafael Winterhalter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-25 19:21:17

Scala SIP-20 proponuje nową implementację Lazy val, która jest bardziej poprawna, ale ~25% wolniejsza niż" aktualna " wersja.

Proponowane wdrożenie wygląda następująco:

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
  }
}
[1]} od czerwca 2013 r. ten SIP nie został zatwierdzony. Spodziewam się, że zostanie ona zatwierdzona i włączona do przyszłej wersji Scali na podstawie dyskusji na liście dyskusyjnej. W związku z tym myślę, że mądrze byłoby zwrócić uwagę na obserwację Daniela śpiewaka :

Lazy val jest* nie * Darmowy (a nawet tani). Używaj go tylko wtedy, gdy absolutnie potrzebujesz lenistwa dla poprawności, nie dla optymalizacji.

 11
Author: Leif Wickland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-26 20:05:55

Napisałem post w tej sprawie https://dzone.com/articles/cost-laziness

W skrócie, kara jest tak mała, że w praktyce można ją zignorować.

 10
Author: Roman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-11 09:34:23

Biorąc pod uwagę bycode generowany przez scala dla leniwych, może wystąpić problem bezpieczeństwa wątku, jak wspomniano w double check locking http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1

 -6
Author: Huy Le,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-15 09:26:58