स्काला की आलसी घाटी की (छिपी हुई) लागत क्या है?


165

स्काला की एक उपयोगी विशेषता है lazy val, जहां एक के मूल्यांकन में valदेरी होती है जब तक यह आवश्यक नहीं है (पहली पहुंच में)।

बेशक, lazy valकुछ ओवरहेड होना चाहिए - कहीं न कहीं स्काला को इस बात का ध्यान रखना चाहिए कि क्या मूल्य का मूल्यांकन पहले से ही किया गया है और मूल्यांकन को सिंक्रनाइज़ किया जाना चाहिए, क्योंकि कई थ्रेड्स एक ही समय में पहली बार मूल्य तक पहुंचने का प्रयास कर सकते हैं।

वास्तव में इसकी लागत क्या है lazy val- क्या एक छुपा बूलियन झंडा है जिसे lazy valट्रैक रखने के लिए जुड़ा हुआ है यदि इसका मूल्यांकन किया गया है या नहीं, क्या वास्तव में सिंक्रनाइज़ किया गया है और क्या कोई और लागत है?

इसके अलावा, मान लीजिए कि मैं ऐसा करता हूं:

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

क्या यह दो अलग-अलग lazy vals के समान है xऔर yक्या मुझे जोड़ी के लिए केवल एक बार ओवरहेड प्राप्त होता है (x, y)?

जवाबों:


86

यह स्कैला मेलिंग सूची से लिया गया है और lazyजावा कोड (बाइटकोड की बजाय) के संदर्भ में कार्यान्वयन विवरण देता है :

class LazyTest {
  lazy val msg = "Lazy"
}

निम्नलिखित जावा कोड के बराबर कुछ के लिए संकलित है:

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
मुझे लगता है कि इस जावा संस्करण को 2007 में पोस्ट किए जाने के बाद से कार्यान्वयन में बदलाव आया होगा। केवल एक ही ब्लॉक है और bitmap$0वर्तमान कार्यान्वयन (2.8) में क्षेत्र अस्थिर है।
मिच Blevins

1
हां - मुझे जो पोस्ट कर रहा था उस पर अधिक ध्यान देना चाहिए था!
oxbow_lakes

8
@ मिच - मुझे उम्मीद है कि कार्यान्वयन बदल गया है! डबल-चेक्ड इनिशियलाइज़ेशन एंटी-पैटर्न एक क्लासिक सूक्ष्म बग है। En.wikipedia.org/wiki/Double-checked_locking
मालवोलियो

20
यह जावा 1.4 तक एंटीपैटर्न था। चूंकि जावा 1.5 वाष्पशील कीवर्ड का थोड़ा सख्त अर्थ है और अब ऐसी दोहरी जांच ठीक है।
किमी किमी २11

8
तो, 2.10 स्कला के रूप में, वर्तमान कार्यान्वयन क्या है? इसके अलावा, क्या कोई यह संकेत दे सकता है कि व्यवहार में ओवरहेड का कितना मतलब है और उपयोग करने के लिए अंगूठे के कुछ नियम, कब से बचें?
84४

39

ऐसा लगता है कि कंपाइलर कई आलसी फ़ील्ड को ध्वजांकित (या नहीं) के रूप में चिह्नित करने के लिए एक क्लास-स्तरीय बिटमैप इंट फ़ील्ड के लिए व्यवस्थित करता है और यदि किसी बिटमैप के संबंधित 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


26

2.10 स्केल के साथ, एक आलसी मूल्य जैसे:

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

निम्नलिखित बाइट कोड से बाइट कोड संकलित किया जाता है:

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मूल्य मेमोरी में फ्लश हो जाए। यह ब्लॉग लेख एक स्पष्टीकरण देता है


11

स्काला एसआईपी -20 आलसी वैल के नए कार्यान्वयन का प्रस्ताव करता है, जो "वर्तमान" संस्करण की तुलना में अधिक सही लेकिन ~ 25% धीमा है।

प्रस्तावित कार्यान्वयन इस तरह दिखता है:

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 तक इस एसआईपी को मंजूरी नहीं दी गई। मुझे उम्मीद है कि मेलिंग सूची चर्चा के आधार पर इसे स्केला के भविष्य के संस्करण में अनुमोदित और शामिल किए जाने की संभावना है। नतीजतन, मुझे लगता है कि आप डैनियल स्पाइवाक के अवलोकन के लिए बुद्धिमान होंगे :

आलसी वैल * न * स्वतंत्र (या सस्ता) है। इसका उपयोग केवल तभी करें जब आपको पूर्णता के लिए आलस्य की आवश्यकता हो, अनुकूलन के लिए नहीं।


10

मैंने इस मुद्दे के संबंध में एक पोस्ट लिखी है https://dzone.com/articles/cost-laziness

संक्षेप में, दंड इतना छोटा है कि व्यवहार में आप इसे अनदेखा कर सकते हैं।


1
उस बेंचमार्क के लिए धन्यवाद। क्या आप SIP-20 प्रस्तावित कार्यान्वयन के खिलाफ भी बेंचमार्क कर सकते हैं?
तुरादग

-6

आलसी के लिए स्काला द्वारा उत्पन्न बायकोड, यह थ्रेड सुरक्षा समस्या का सामना कर सकता है जैसा कि डबल चेक लॉकिंग में उल्लेख किया गया है http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1


3
यह दावा भी मिच द्वारा स्वीकार किए गए जवाब के लिए एक टिप्पणी द्वारा किया गया था और @iirekm द्वारा मना कर दिया गया था: यह पैटर्न java1.5 के बाद से ठीक है।
जेन स्काउडर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.