2010-06-14 17 views
145

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

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

क्या वास्तव में एक lazy val की लागत है - वहाँ एक छिपे हुए बूलियन एक lazy val के साथ जुड़े ट्रैक रखने के लिए अगर यह मूल्यांकन किया गया है या नहीं झंडा है, वास्तव में क्या सिंक्रनाइज़ और वहाँ किसी भी अधिक खर्च कर रहे हैं रहा है?

इसके अलावा, मैं यह कर लगता है:

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

इस जैसा ही होता है दो अलग-अलग lazy val रों x और y या मैं जोड़ी (x, y) के लिए केवल एक बार भूमि के ऊपर मिलता है,?

उत्तर

77

यह जावा कोड (बल्कि बाईटकोड से) के मामले में scala mailing list से लिया और 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; 
    } 

} 
:

class LazyTest { 
    lazy val msg = "Lazy" 
} 

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

+30

मुझे लगता है कि इस जावा संस्करण को 2007 में पोस्ट करने के बाद से कार्यान्वयन बदलना चाहिए था। केवल एक सिंक्रनाइज़ ब्लॉक है और 'बिटमैप $ 0' फ़ील्ड वर्तमान कार्यान्वयन (2.8) में अस्थिर है। –

+1

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

+0

तो, मूल रूप से, इसका मतलब है कि पहली बार आलसी मूल्य तक पहुंच प्रत्यक्ष मूल्य से अधिक धीमी है (एक अजीब मामलों में भी डेडलॉक्स बना सकता है), लेकिन बाद में पहुंच गैर-आलसी मानों की तुलना में शायद ही धीमी है। ऐसा लगता है कि इसे हल्के ढंग से नहीं लिया जाना चाहिए, केवल महंगी शुरुआत के लिए। –

38

ऐसा लगता है कि कंपाइलर क्लास-स्तरीय बिटमैप इंट फील्ड के लिए कई आलसी फ़ील्ड को प्रारंभ करने (या नहीं) के रूप में फ़्लैग करने के लिए व्यवस्थित करता है और बिटमैप के प्रासंगिक xor को इंगित करता है तो सिंक्रनाइज़ किए गए ब्लॉक में लक्ष्य फ़ील्ड को प्रारंभ करता है।

का उपयोग करना:

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 में आद्याक्षरित तरह lazy val (x,y) = { ... } ही तंत्र के माध्यम से कैशिंग नेस्ट है

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

नमूना बाईटकोड पैदा करता है। ट्यूपल परिणाम का आलसी मूल्यांकन और कैश किया जाता है, और एक्स या वाई का उपयोग टुपल मूल्यांकन को ट्रिगर करेगा। ट्यूपल से व्यक्तिगत मूल्य का निष्कर्षण स्वतंत्र रूप से और आलसी (और कैश किया जाता है) किया जाता है। तो उपरोक्त डबल-इंस्टेंटेशन कोड x, y उत्पन्न करता है, और प्रकार के x$1 फ़ील्ड उत्पन्न करता है।

-6

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

+2

यह दावा मिच द्वारा स्वीकार किए गए उत्तर पर टिप्पणी के द्वारा भी किया गया था और @iirekm द्वारा अस्वीकार किया गया था: यह पैटर्न java1.5 से ठीक है। –

11

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

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

22

स्काला 2.10, की तरह एक आलसी मूल्य के साथ:

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 का प्रतिनिधित्व करती है:

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

बाइट कोड है जो निम्न जावा कोड जैसा दिखता है के लिए संकलित किया गया है । यदि आप कोई अन्य फ़ील्ड जोड़ते हैं, तो कंपाइलर कम से कम 2 मानों का प्रतिनिधित्व करने में सक्षम होने के लिए फ़ील्ड के आकार को बढ़ाएगा, यानी byte के रूप में। यह सिर्फ विशाल वर्गों के लिए चला जाता है।

लेकिन आपको आश्चर्य हो सकता है कि यह क्यों काम करता है? सिंक्रनाइज़ किए गए ब्लॉक में प्रवेश करते समय थ्रेड-स्थानीय कैश को साफ़ किया जाना चाहिए, जैसे गैर-अस्थिर x मान स्मृति में फ़्लश किया गया है। यह ब्लॉग आलेख an explanation देता है।

8

मैं इस मुद्दे https://dzone.com/articles/cost-laziness

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

+1

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

संबंधित मुद्दे