खैर, यह वास्तव में क्या मानचित्र के वास्तविक प्रकार आप उपयोग कर रहे पर निर्भर करता है। शायद HashMap
। अब, पूर्व-आवंटित स्मृति द्वारा उस लाभ प्रदर्शन जैसे म्यूटेबल संरचनाओं का उपयोग करने की अपेक्षा करता है। आप दस लाख मानचित्रों में शामिल हो रहे हैं, इसलिए अंतिम नक्शा कुछ हद तक बड़ा होना चाहिए। चलो देखते हैं कि कैसे इन कुंजी/मान जोड़ दिए जाते हैं:
protected def addEntry(e: Entry) {
val h = index(elemHashCode(e.key))
e.next = table(h).asInstanceOf[Entry]
table(h) = e
tableSize = tableSize + 1
if (tableSize > threshold)
resize(2 * table.length)
}
resize
लाइन में 2 *
देखते हैं? म्यूटेबल HashMap
हर बार अंतरिक्ष से बाहर होने पर दोगुना हो जाता है, जबकि अपरिवर्तनीय स्मृति उपयोग में बहुत रूढ़िवादी होता है (हालांकि मौजूदा कुंजी आमतौर पर अपडेट होने पर दो बार स्थान पर कब्जा कर लेती है)।
अब, अन्य प्रदर्शन समस्याओं के लिए, आप पहले दो संस्करणों में कुंजी और मानों की एक सूची बना रहे हैं। इसका मतलब है कि, किसी भी मानचित्र में शामिल होने से पहले, आपके पास पहले से ही प्रत्येक Tuple2
(कुंजी/मान जोड़े) स्मृति में दो बार है! इसके अलावा List
का ओवरहेड, जो छोटा है, लेकिन हम ओवरहेड के दस लाख से अधिक तत्वों के बारे में बात कर रहे हैं।
आप एक प्रक्षेपण का उपयोग करना चाह सकते हैं, जो इससे बचाता है। दुर्भाग्यवश, प्रक्षेपण Stream
पर आधारित है, जो स्कैला 2.7.x पर हमारे उद्देश्यों के लिए बहुत विश्वसनीय नहीं है। एक मूल्य की गणना नहीं करता है जब तक यह आवश्यक है
for (m <- listOfMaps.projection; kv <- m) yield kv
एक Stream
: फिर भी ऐसा करें। कचरा कलेक्टर को अप्रयुक्त तत्वों को भी एकत्र करना चाहिए, जब तक आप Stream
के सिर का संदर्भ न रखें, जो आपके एल्गोरिदम में ऐसा लगता है।
संपादित
पूरक के रूप में एक के लिए/उपज समझ एक या अधिक संग्रह लेता है और एक नया संग्रह लौट आते हैं। जितनी बार यह समझ में आता है, लौटने वाला संग्रह मूल संग्रह के समान प्रकार का होता है। इसलिए, उदाहरण के लिए, निम्नलिखित कोड में, समझदारी एक नई सूची बनाती है, जिसे l2
के अंदर संग्रहीत किया जाता है। यह val l2 =
नहीं है जो नई सूची बनाता है, लेकिन समझ के लिए।
val l = List(1,2,3)
val l2 = for (e <- l) yield e*2
अब, चलो कोड को देखो पहले दो एल्गोरिदम में इस्तेमाल किया जा रहा (ऋण mutable
कीवर्ड):
(Map[A,B]() /: (for (m <- listOfMaps; kv <-m) yield kv))
foldLeft
ऑपरेटर, यहाँ के साथ अपने /:
पर्याय लिखा है, पर सक्रिय किया जाएगा वस्तु समझ के लिए लौट आया। याद रखें कि ऑपरेटर के अंत में :
ऑब्जेक्ट और पैरामीटर के क्रम को बदल देता है।
अब, मान लीजिए कि यह कौन सा ऑब्जेक्ट है, जिस पर foldLeft
कहा जा रहा है। इस समझ में पहला जनरेटर m <- listOfMaps
है। हम जानते हैं कि listOfMaps
टाइप सूची [एक्स] का एक संग्रह है, जहां एक्स वास्तव में यहां प्रासंगिक नहीं है। List
पर एक समझ के परिणाम हमेशा एक और List
है। अन्य जेनरेटर प्रासंगिक नहीं हैं।
तो आप इस List
लेते हैं, प्रत्येक Map
जो इस List
का एक घटक है अंदर सभी कुंजी/मान मिलता है, और एक नया List
कि सभी के साथ हैं। यही कारण है कि आप अपनी सारी चीजों को डुप्लिकेट कर रहे हैं।
(वास्तव में, यह उससे भी बदतर है, क्योंकि प्रत्येक जनरेटर एक नया संग्रह बनाता है, दूसरा जनरेटर द्वारा बनाए गए संकलन सिर्फ listOfMaps
के प्रत्येक तत्व का आकार है, हालांकि, और तुरंत उपयोग के बाद उन्हें छोड़ दिया जाता)
अगला प्रश्न - वास्तव में, पहला, लेकिन उत्तर को उलटा करना आसान था - यह है कि projection
का उपयोग कैसे मदद करता है।
जब आप projection
पर List
पर कॉल करते हैं, तो यह Stream
(स्कैला 2.7.x पर) के प्रकार का नया ऑब्जेक्ट देता है। सबसे पहले आप सोच सकते हैं कि यह केवल चीजों को और खराब कर देगा, क्योंकि अब आपके पास List
की तीन प्रतियां होंगी, बजाय एक की बजाय। लेकिन Stream
पूर्व-गणना नहीं है। यह आलसी गणना की गई है।
क्या है कि इसका मतलब है कि जिसके परिणामस्वरूप वस्तु, Stream
, नहीं List
की एक प्रति, बल्कि, एक समारोह है कि Stream
गणना करने के लिए जरूरत पड़ने पर इस्तेमाल किया जा सकता है, लेकिन है। एक बार गणना की गई, परिणाम रखा जाएगा ताकि इसे फिर से गणना करने की आवश्यकता न हो।
इसके अलावा, map
, flatMap
और एक Stream
सभी की filter
जिसका मतलब है कि आप उन सब को एक साथ श्रृंखला कर सकते हैं List
जो उन्हें बनाया की एक प्रतिलिपि बनाने के बिना एक नया Stream
लौटने के लिए,। चूंकि yield
के साथ समझ के लिए इन कार्यों का उपयोग करें, डेटा की अनावश्यक प्रतियों को रोकने के अंदर Stream
का उपयोग करें।
अब, मान लीजिए आप कुछ इस तरह लिखा है:
val kvs = for (m <- listOfMaps.projection; kv <-m) yield kv
(Map[A,B]() /: kvs) { ... }
इस मामले तुम कुछ भी प्राप्त कर रहा नहीं जाता है। Stream
को kvs
पर निर्दिष्ट करने के बाद, डेटा को अभी तक कॉपी नहीं किया गया है। एक बार दूसरी पंक्ति निष्पादित हो जाने के बाद, केवीएस ने अपने प्रत्येक तत्व की गणना की होगी, और इसलिए, डेटा की पूरी प्रतिलिपि होगी।
अब मूल रूप ::
(Map[A,B]() /: (for (m <- listOfMaps.projection; kv <-m) yield kv))
इस मामले में, Stream
एक ही समय में यह गणना की जाती है पर प्रयोग किया जाता है पर विचार करें। के कुछ समय के लिए Stream
के लिए foldLeft
कैसे परिभाषित किया गया है पर नजर डालते हैं:
override final def foldLeft[B](z: B)(f: (B, A) => B): B = {
if (isEmpty) z
else tail.foldLeft(f(z, head))(f)
}
तो Stream
खाली है, बस संचायक वापसी। अन्यथा, एक नए संचयक (f(z, head)
) की गणना करें और फिर इसे Stream
के tail
पर फ़ंक्शन पास करें।
एक बार f(z, head)
निष्पादित हो गया है, हालांकि, head
का कोई शेष संदर्भ नहीं होगा। या, दूसरे शब्दों में, कार्यक्रम में कहीं भी Stream
के लिए इंगित नहीं करेगा, और इसका मतलब है कि कचरा कलेक्टर इसे इकट्ठा कर सकता है, इस प्रकार स्मृति को मुक्त कर सकता है।
अंतिम परिणाम यह है कि समझ के द्वारा उत्पादित प्रत्येक तत्व केवल संक्षेप में मौजूद होगा, जबकि आप इसे संचयक की गणना करने के लिए उपयोग करेंगे। और इस तरह आप अपने पूरे डेटा की प्रतिलिपि रखते हुए सहेजते हैं।
अंत में, सवाल यह है कि तीसरे एल्गोरिदम से इसका लाभ क्यों नहीं मिलता है। खैर, तीसरा एल्गोरिदम yield
का उपयोग नहीं करता है, इसलिए किसी भी डेटा की कोई भी प्रतिलिपि नहीं बनाई जा रही है। इस मामले में, projection
का उपयोग केवल एक संकेतक परत जोड़ता है।
दिलचस्प। मैंने आपकी सलाह का पालन किया और सूचियों को स्ट्रीम में स्विच किया, और इसमें कोई प्रदर्शन सुधार नहीं हुआ। वास्तव में, अनिवार्य कार्यान्वयन आवश्यक समय में दोगुना हो गया। हालांकि, उत्परिवर्तनीय कार्यान्वयन स्मृति से बाहर चलना बंद कर दिया। – Jeff
इसके अलावा, शायद यह एक नोबिश सवाल है, लेकिन पहले दो संस्करण कुंजी और मूल्यों की सूचियां कैसे बनाते हैं? क्या ऐसा इसलिए है क्योंकि() उपज समझ एक नई सूची बनाता है? मैं स्पष्ट हूं कि कैसे अभिव्यक्ति के अंदर कॉलिंग। प्रोजेक्शन() के अंदर यह भी बचाता है। क्या ऐसा इसलिए है क्योंकि समझ एक ही प्रकार का अनुक्रम देता है जिसे पारित किया गया था? – Jeff
मैं अपने उत्तर का पूरक हूं, क्योंकि यह टिप्पणी के रूप में रखने के लिए बहुत अधिक है। –