Scala'nın tembel valinin (gizli) maliyeti nedir?


165

Scala'nın kullanışlı bir özelliği, a'nın lazy valdeğerlendirilmesinin valgerekli olana kadar ertelenmesidir (ilk erişimde).

Tabii ki, bir lazy valek yük olmalıdır - bir yerde Scala, değerin önceden değerlendirilip değerlendirilmediğini ve değerlendirmenin senkronize edilmesi gerektiğini takip etmelidir, çünkü birden çok iş parçacığı aynı anda ilk kez değere erişmeye çalışabilir.

A'nın maliyeti tam olarak nedir lazy val- lazy valdeğerlendirilip değerlendirilmediğini takip etmek için a ile ilişkili gizli bir boole bayrağı var mı, tam olarak senkronize edilen nedir ve daha fazla maliyet var mı?

Ayrıca, bunu yaptığımı varsayalım:

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

Bu iki ayrı lazy vals xile aynı mıdır yve çift için yükü sadece bir kez (x, y)mi alabilirim?

Yanıtlar:


86

Bu, scala posta listesinden alınır ve lazyJava kodu (bayt kodu yerine) açısından uygulama ayrıntılarını verir :

class LazyTest {
  lazy val msg = "Lazy"
}

aşağıdaki Java koduna eşdeğer bir şeyle derlenmiştir:

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

}

33
Bu Java sürümü 2007 yılında yayınlanmıştır beri uygulamanın değişmiş olması gerektiğini düşünüyorum. Sadece bir senkronize blok var ve bitmap$0alan geçerli uygulamada uçucu (2.8).
Mitch Blevins

1
Evet - Yayınladığım şeye daha fazla dikkat etmeliydim!
oxbow_lakes

8
@Mitch - Umarım uygulama değişmiştir! Çift denetimli başlatma anti-paterni klasik ince bir hatadır. Bkz. En.wikipedia.org/wiki/Double-checked_locking
Malvolio

20
Java 1.4'e kadar antipatterndi. Java 1.5 geçici anahtar kelimesinin biraz daha katı bir anlamı olduğundan ve şimdi böyle bir çift kontrol tamamdır.
iirekm

8
Peki, scala 2.10'dan itibaren mevcut uygulama nedir? Ayrıca, birileri bunun pratikte ne kadar genel gider anlamına geldiğine ve ne zaman kullanılacağına, ne zaman kaçınacağına dair bir ipucu verebilir mi?
ib84

39

Derleyicinin, sınıf düzeyinde bir bitmap int alanının, birden çok tembel alanı ilklendirilmiş (veya edilmemiş) olarak işaretlemesi için ayarladığı ve bitmap'in ilgili xor değerinin gerekli olduğunu belirtmesi durumunda, senkronize edilmiş bir bloktaki hedef alanını başlatır gibi görünüyor.

Kullanımı:

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

örnek bayt kodu üretir:

 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

Tuples gibi başlayan değerler lazy val (x,y) = { ... }, aynı mekanizma yoluyla iç içe geçmiş önbelleklemiştir. Tuple sonucu tembel olarak değerlendirilir ve önbelleğe alınır ve x veya y'den bir erişim tuple değerlendirmesini tetikler. Tupledan bireysel değerin çıkarılması bağımsız ve tembel olarak yapılır (ve önbelleğe alınır). Bu nedenle yukarıdaki çift örnekleme kodu x, bir y, ve x$1tür alanı oluşturur Tuple2.


26

Scala 2.10 ile tembel bir değer:

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

, aşağıdaki Java koduna benzeyen bayt koduyla derlenmiştir:

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

Bitmap'in a ile temsil edildiğine dikkat edin boolean. Başka bir alan eklerseniz, derleyici en az 2 değeri, yani a olarak temsil edebilmek için alanın boyutunu artıracaktır byte. Bu sadece büyük sınıflar için devam ediyor.

Ama bunun neden işe yaradığını merak ediyor olabilirsiniz? Senkronize bir bloğa girilirken uçucu olmayan xdeğer belleğe akacak şekilde yerel-yerel önbellekler temizlenmelidir . Bu blog makalesi bir açıklama yapıyor .


11

Scala SIP-20 , daha geçerli ancak "mevcut" versiyondan ~% 25 daha yavaş olan yeni bir tembel val uygulaması önermektedir.

Önerilen uygulama görünüyor gibi:

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

Haziran 2013 itibariyle bu SIP onaylanmamıştır. Posta listesi tartışmasına dayanarak Scala'nın gelecekteki bir sürümüne onaylanmasının ve dahil edilmesinin muhtemel olduğunu umuyorum. Sonuç olarak, Daniel Spiewak'ın gözlemine kulak vermenin akıllıca olacağını düşünüyorum :

Tembel val * değil * ücretsiz (hatta ucuz). Optimizasyon için değil, sadece doğruluk için tembelliğe ihtiyacınız varsa kullanın.



Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.