C #: File.ReadLines () बनाम File.ReadAllLines () - और मुझे क्यों परवाह करनी चाहिए?

कुछ हफ़्ते पहले, मैं और मेरे साथ काम करने वाली दो टीमें बड़ी पाठ फ़ाइलों को संसाधित करने के कुशल तरीकों के बारे में चर्चा में आई थीं।

इससे इस विषय के बारे में अतीत में हुई कुछ अन्य पिछली चर्चाएँ और, विशेष रूप से C # में पैदावार वापसी के उपयोग के बारे में (जिससे मैं शायद भविष्य के ब्लॉग पोस्ट में बात करूँगा) को ट्रिगर किया। इसलिए, मैंने सोचा कि जब डेटा के बड़े पैमाने पर प्रसंस्करण की बात आती है तो C # प्रभावी ढंग से कैसे बढ़ सकता है, यह प्रदर्शित करना एक अच्छी चुनौती होगी।

चुनौती

इसलिए, चर्चा के तहत समस्या यह है:

  • मान लीजिए कि एक बड़ी CSV फ़ाइल है, शुरुआत के लिए ~ 500MB कहें
  • कार्यक्रम को फ़ाइल की प्रत्येक पंक्ति के माध्यम से जाना चाहिए, इसे पार्स करना चाहिए और कुछ मानचित्र बनाना / आधारित गणनाओं को कम करना चाहिए

और चर्चा में इस बिंदु पर सवाल यह है:

उस कोड को लिखने का सबसे कुशल तरीका क्या है जो इस लक्ष्य को पूरा करने में सक्षम है? इसका अनुपालन करते समय:
i) उपयोग की गई मेमोरी की मात्रा कम से कम और
ii) प्रोग्राम के कोड की लाइनों को कम करें (एक उचित सीमा तक, निश्चित रूप से)

तर्क के लिए, हम StreamReader का उपयोग कर सकते हैं, लेकिन इससे अधिक कोड लिखने की आवश्यकता होगी और वास्तव में, C # में पहले से ही File.ReadAllLines () और File.ReadLines () सुविधा विधियाँ हैं। तो हमें उन का उपयोग करना चाहिए!

मुझे कोड दिखाओ

उदाहरण के लिए, आइए एक कार्यक्रम पर विचार करें:

  1. एक पाठ फ़ाइल को इनपुट के रूप में लेता है जहां प्रत्येक पंक्ति पूर्णांक होती है
  2. फ़ाइल में सभी संख्याओं के योग की गणना करता है

इस उदाहरण के लिए, हम बहुत सत्यापन संदेश छोड़ देंगे :-)

C # में यह निम्नलिखित कोड द्वारा पूरा किया जा सकता है:

var sumOfLines = File.ReadAllLines (filePath)
    चयन करें (पंक्ति => int.Parse (पंक्ति))
    .Sum ()

बहुत आसान है, है ना?

जब हम इस कार्यक्रम को एक बड़ी फ़ाइल के साथ खिलाते हैं तो क्या होता है?

यदि हम 100MB फ़ाइल को संसाधित करने के लिए इस प्रोग्राम को चलाते हैं, तो यह वही है जो हमें मिलता है:

  • 2GB RAM ने इस कंप्यूटिंग को पूरा करने के लिए मेमोरी की खपत की
  • जीसी के बहुत सारे (प्रत्येक पीला आइटम एक जीसी रन है)
  • निष्पादन को पूरा करने के लिए 18 सेकंड
BTW, इस कोड के लिए एक 500MB फ़ाइल खिलाने के कारण कार्यक्रम OutOfMemoryException मज़ा, सही के साथ दुर्घटनाग्रस्त हो गया?

अब इसके बजाय File.ReadLines () की कोशिश करें

चलिए File.ReadLines () के बजाय File.ReadLines () का उपयोग करने के लिए कोड को बदलें और देखें कि यह कैसे जाता है:

var sumOfLines = File.ReadLines (filePath)
    चयन करें (पंक्ति => int.Parse (पंक्ति))
    .Sum ()

इसे चलाते समय, अब हमें मिलता है:

  • 2GB के बजाय 12MB RAM की खपत! (!!)
  • केवल 1 GC चला
  • 18 के बजाय 10 सेकंड पूरे करने हैं

ये क्यों हो रहा है?

टीएल; डीआर महत्वपूर्ण अंतर यह है कि File.ReadAllLines () एक स्ट्रिंग का निर्माण कर रहा है [] जिसमें फ़ाइल की प्रत्येक पंक्ति होती है, जिसमें संपूर्ण फ़ाइल को लोड करने के लिए पर्याप्त मेमोरी की आवश्यकता होती है; File.ReadLines () के विपरीत जो एक समय में प्रत्येक पंक्ति को प्रोग्राम को फीड करता है, जिसमें केवल एक लाइन लोड करने के लिए मेमोरी की आवश्यकता होती है।

थोड़ा और विस्तार से:

File.ReadAllLines () एक बार में पूरी फ़ाइल पढ़ता है और एक स्ट्रिंग देता है [] जहाँ सरणी का प्रत्येक आइटम फ़ाइल की एक पंक्ति से मेल खाता है। इसका मतलब है कि प्रोग्राम को फ़ाइल से सामग्री लोड करने के लिए फ़ाइल के आकार के रूप में अधिक मेमोरी की आवश्यकता है। साथ ही सभी स्ट्रिंग तत्वों को इंट्रोड्यूस करने और फिर योग () की गणना करने के लिए आवश्यक मेमोरी

दूसरी तरफ, File.ReadLines () फ़ाइल पर एक एन्यूमरेटर बनाता है, इसे लाइन-बाय-लाइन (वास्तव में StreamReader.ReadLine () का उपयोग करके) पढ़ता है। इसका मतलब यह है कि प्रत्येक पंक्ति को लाइन-बी-लाइन मोड पर आंशिक योग में पढ़ा, परिवर्तित और जोड़ा जाता है।

निष्कर्ष

यह विषय निम्न-स्तरीय कार्यान्वयन विवरण की तरह लग सकता है, लेकिन यह वास्तव में बहुत महत्वपूर्ण है क्योंकि यह निर्धारित करता है कि एक बड़े डेटा सेट के साथ खिलाए जाने पर कोई प्रोग्राम कैसे स्केल करेगा।

सॉफ़्टवेयर डेवलपर्स के लिए इस तरह की स्थितियों का अनुमान लगाने में सक्षम होना महत्वपूर्ण है, क्योंकि किसी को कभी नहीं पता होता है कि कोई व्यक्ति एक बड़ा इनपुट प्रदान करने जा रहा है जो विकास के चरण पर नहीं था।

इसके अलावा, LINQ इन दोनों परिदृश्यों को मूल रूप से संभालने के लिए पर्याप्त लचीला है और कोड के साथ उपयोग किए जाने पर उत्कृष्ट दक्षता प्रदान करता है जो मूल्यों की "स्ट्रीमिंग" प्रदान करता है।

इसका मतलब यह है कि हर चीज को लिस्ट या T [] नहीं होना चाहिए, जिसका अर्थ है कि पूरा डेटा सेट मेमोरी में लोड हो गया है। IEnumerable का उपयोग करके हम अपने कोड को उन तरीकों से उपयोग करने के लिए सामान्य बनाते हैं जो स्मृति में संपूर्ण डेटा सेट प्रदान करते हैं या "स्ट्रीमिंग" मोड पर मान प्रदान करते हैं।