समानांतर अनंत जावा स्ट्रीम मेमोरी से बाहर चलती हैं


16

मैं यह समझने की कोशिश कर रहा हूं कि निम्नलिखित जावा प्रोग्राम एक क्यों देता है OutOfMemoryError, जबकि इसके बिना संबंधित प्रोग्राम .parallel()नहीं है।

System.out.println(Stream
    .iterate(1, i -> i+1)
    .parallel()
    .flatMap(n -> Stream.iterate(n, i -> i+n))
    .mapToInt(Integer::intValue)
    .limit(100_000_000)
    .sum()
);

मेरे दो सवाल हैं:

  1. इस कार्यक्रम का इरादा आउटपुट क्या है?

    इसके बिना .parallel()ऐसा लगता है कि यह सीधे तौर पर आउटपुट sum(1+2+3+...)का मतलब है कि इसका मतलब यह है कि फ्लैटप्लस में पहली धारा पर "अटक जाता है", जो समझ में आता है।

    समानांतर के साथ मुझे नहीं पता कि क्या एक अपेक्षित व्यवहार है, लेकिन मेरा अनुमान है कि यह किसी भी तरह से पहले nया तो धाराओं को वर्गीकृत करता है, जहां nसमानांतर श्रमिकों की संख्या है। यह चंकिंग / बफरिंग व्यवहार के आधार पर थोड़ा अलग भी हो सकता है।

  2. क्या यह स्मृति से बाहर चलाने का कारण बनता है? मैं विशेष रूप से यह समझने की कोशिश कर रहा हूं कि इन धाराओं को हुड के तहत कैसे लागू किया जाता है।

    मैं अनुमान लगा रहा हूं कि कुछ धारा को अवरुद्ध करता है, इसलिए यह कभी खत्म नहीं होता है और उत्पन्न मूल्यों से छुटकारा पाने में सक्षम है, लेकिन मुझे यह नहीं पता है कि किन चीजों का मूल्यांकन किया जाता है और जहां बफरिंग होती है।

संपादित करें: यदि यह प्रासंगिक है, तो मैं जावा 11 का उपयोग कर रहा हूं।

एडिट 2: जाहिर तौर पर सिंपल प्रोग्राम के लिए भी यही होता है IntStream.iterate(1,i->i+1).limit(1000_000_000).parallel().sum(), इसलिए इसके limitबजाय आलस के साथ करना पड़ सकता है flatMap


समानांतर () आंतरिक रूप से ForkJoinPool का उपयोग करता है। मुझे लगता है कि ForkJoin फ्रेमवर्क जावा में जावा 7 से है
अरविंद

जवाबों:


9

आप कहते हैं, " लेकिन मैं यह नहीं जानता कि किस क्रम में चीजों का मूल्यांकन किया जाता है और कहाँ बफरिंग होती है ", जो कि समानांतर धाराओं के बारे में ठीक है। मूल्यांकन का क्रम अनिर्दिष्ट है।

आपके उदाहरण का एक महत्वपूर्ण पहलू है .limit(100_000_000)। तात्पर्य यह है कि कार्यान्वयन केवल मनमाने मूल्यों का योग नहीं कर सकता है, लेकिन पहले 100,000,000 संख्याओं का योग करना चाहिए । ध्यान दें कि संदर्भ कार्यान्वयन में, .unordered().limit(100_000_000)परिणाम नहीं बदलता है, जो इंगित करता है कि अनियंत्रित मामले के लिए कोई विशेष कार्यान्वयन नहीं है, लेकिन यह कार्यान्वयन विवरण है।

अब, जब कार्यकर्ता सूत्र तत्वों को संसाधित करते हैं, तो वे उन्हें योग नहीं कर सकते हैं, क्योंकि उन्हें यह जानना होगा कि उन्हें किन तत्वों का उपभोग करने की अनुमति है, जो इस बात पर निर्भर करता है कि कितने तत्व उनके विशिष्ट कार्यभार से पहले हैं। चूंकि यह धारा आकारों को नहीं जानती है, यह केवल तभी ज्ञात हो सकता है जब उपसर्ग तत्वों को संसाधित किया गया हो, जो कभी भी अनंत धाराओं के लिए नहीं होता है। तो कार्यकर्ता सूत्र पल भर के लिए बफ़र करता रहता है, यह जानकारी उपलब्ध हो जाती है।

सिद्धांत रूप में, जब एक श्रमिक सूत्र जानता है कि यह बाईं ओर के कार्य-चंक को संसाधित करता है, तो यह तत्वों को तुरंत योग कर सकता है, उन्हें गिन सकता है, और सीमा तक पहुंचने पर अंत का संकेत दे सकता है। इसलिए स्ट्रीम समाप्त हो सकती है, लेकिन यह बहुत सारे कारकों पर निर्भर करता है।

आपके मामले में, एक प्रशंसनीय परिदृश्य यह है कि अन्य श्रमिक सूत्र बफ़र्स को आवंटित करने में तेजी से काम कर रहे हैं, जबकि वामपंथी काम की गिनती कर रहे हैं। इस परिदृश्य में, समय के लिए सूक्ष्म परिवर्तन धारा को कभी-कभी मान के साथ वापस कर सकते हैं।

जब हम सभी मजदूर धागे को धीमा कर देते हैं, तो सबसे बाईं ओर के प्रसंस्करण को छोड़कर, हम धारा को समाप्त कर सकते हैं (कम से कम अधिकांश रन में):

System.out.println(IntStream
    .iterate(1, i -> i+1)
    .parallel()
    .peek(i -> { if(i != 1) LockSupport.parkNanos(1_000_000_000); })
    .flatMap(n -> IntStream.iterate(n, i -> i+n))
    .limit(100_000_000)
    .sum()
);

¹ मैं स्टुअर्ट मार्क्स के एक सुझाव का पालन ​​कर रहा हूं जब प्रसंस्करण आदेश के बजाय मुठभेड़ आदेश के बारे में बात करने के लिए बाएं से दाएं क्रम का उपयोग किया जाता है।


बहुत अच्छा जवाब! मुझे आश्चर्य है कि अगर कोई जोखिम भी है कि सभी थ्रेड्स फ्लैटपाइप ऑपरेशन चलाने लगते हैं, और कोई भी वास्तव में बफ़र्स (संक्षेप) को खाली करने के लिए आवंटित नहीं होता है? मेरे वास्तविक उपयोग के मामले में अनंत धाराएँ स्मृति में रखने के लिए बहुत बड़ी फाइलें हैं। मुझे आश्चर्य है कि मैं मेमोरी के उपयोग को कम रखने के लिए स्ट्रीम को फिर से कैसे लिख सकता हूं?
थॉमस अहले

1
क्या आप उपयोग कर रहे हैं Files.lines(…)? यह जावा 9 में काफी सुधार किया गया है
होल्टर

1
यह जावा 8 में क्या करता है। नए जेआरई में, यह अभी भी BufferedReader.lines()कुछ परिस्थितियों में वापस आ जाएगा (डिफ़ॉल्ट फाइलसिस्टम नहीं, एक विशेष चारसेट या आकार से बड़ा Integer.MAX_FILES)। यदि इनमें से एक लागू होता है, तो एक कस्टम समाधान मदद कर सकता है। यह एक नया Q & A के लायक होगा ...
Holger

1
Integer.MAX_VALUEबेशक ...
होल्गर

1
बाहरी स्ट्रीम, फ़ाइलों की एक धारा क्या है? क्या इसका कोई अनुमानित आकार है?
होल्गर

5

मेरा सबसे अच्छा अनुमान यह है कि parallel()आंतरिक व्यवहार को बदलना, flatMap()जिसमें पहले से ही आलसी का मूल्यांकन करने में समस्याएं थीं

OutOfMemoryErrorत्रुटि है कि आप हो रही में सूचना मिली थी रहे हैं [JDK-8202307] एक java.lang.OutOfMemoryError हो रही है:। जब Stream.iterator बुला जावा ढेर अंतरिक्ष () अगले () एक धारा जो flatMap में एक अनंत / बहुत बड़ा स्ट्रीम का उपयोग करता है पर । यदि आप टिकट को देखते हैं तो यह कमोबेश उसी स्टैक ट्रेस का है जो आपको मिल रहा है। निम्नलिखित कारणों से टिकट को बंद नहीं किया जाएगा:

iterator()और spliterator()विधियों "भागने hatches" प्रयोग की जाने वाली हैं, जब यह अन्य कार्यों का उपयोग करना संभव नहीं है। उनकी कुछ सीमाएँ हैं, क्योंकि वे उस धारा के कार्यान्वयन का एक धक्का मॉडल है जो एक पुल मॉडल में बदल जाता है। इस तरह के संक्रमण के लिए कुछ मामलों में बफरिंग की आवश्यकता होती है, जैसे कि जब कोई तत्व (फ्लैट) दो या अधिक तत्वों के लिए मैप किया जाता है । यह तत्व के उत्पादन की नेस्टेड परतों के माध्यम से कितने तत्वों को खींचने के लिए बैक-प्रेशर की धारणा का समर्थन करने के लिए, सामान्य मामलों की कीमत पर, धारा के कार्यान्वयन को महत्वपूर्ण रूप से जटिल करेगा।


यह बहुत दिलचस्प है! यह समझ में आता है कि पुश / पुल संक्रमण के लिए बफरिंग की आवश्यकता होती है जो मेमोरी का उपयोग कर सकती है। हालांकि मेरे मामले में ऐसा लगता है कि बस पुश का उपयोग ठीक काम करना चाहिए और बस शेष तत्वों को छोड़ देना चाहिए जैसा कि वे दिखाई देते हैं? या हो सकता है कि आप कह रहे हों कि फ्लैपमैप बनाने के लिए एक पुनरावृत्ति का कारण बनता है?
थॉमस अहले

3

OOME धारा के अनंत होने के कारण नहीं है, बल्कि इस तथ्य से है कि यह नहीं है

यानी, यदि आप टिप्पणी करते हैं .limit(...), तो यह कभी भी स्मृति से बाहर नहीं होगी - लेकिन निश्चित रूप से, यह कभी भी समाप्त नहीं होगी।

एक बार जब यह विभाजित हो जाता है, तो धारा केवल तत्वों की संख्या पर नज़र रख सकती है यदि वे प्रत्येक थ्रेड के भीतर जमा होते हैं (लगता है कि वास्तविक संचायक है Spliterators$ArraySpliterator#array)।

ऐसा लगता है कि आप इसे बिना पुन: उत्पन्न कर सकते हैं flatMap, बस इसके साथ निम्नलिखित को चलाएं -Xmx128m:

    System.out.println(Stream
            .iterate(1, i -> i + 1)
            .parallel()
      //    .flatMap(n -> Stream.iterate(n, i -> i+n))
            .mapToInt(Integer::intValue)
            .limit(100_000_000)
            .sum()
    );

हालाँकि, limit()यह टिप्पणी करने के बाद , यह तब तक ठीक चलना चाहिए जब तक आप अपने लैपटॉप को खाली करने का निर्णय नहीं लेते।

वास्तविक कार्यान्वयन विवरणों के अलावा, यहां मुझे लगता है कि क्या हो रहा है:

साथ limit, sumकम करने, योग करने के लिए तो कोई धागा आंशिक योग का उत्सर्जन कर सकते हैं पहले एक्स तत्वों चाहता है। प्रत्येक "स्लाइस" (थ्रेड) को तत्वों को संचित करने और उन्हें पारित करने की आवश्यकता होगी। सीमा के बिना, ऐसा कोई अड़चन नहीं है, इसलिए प्रत्येक "स्लाइस" केवल उन तत्वों से आंशिक योग की गणना करेगा जो इसे (हमेशा के लिए) मिलते हैं, यह मानते हुए कि यह अंततः परिणाम का उत्सर्जन करेगा।


"विभाजित होने के बाद" आपका क्या मतलब है? क्या सीमा इसे किसी तरह विभाजित करती है?
थॉमस अहले

@ThomasAhle समानता प्राप्त करने के लिए आंतरिक रूप से parallel()उपयोग करेगा ForkJoinPoolSpliteratorप्रत्येक के लिए असाइन काम करने के लिए इस्तेमाल किया जाएगा ForkJoinकार्य, मुझे लगता है हम "विभाजन" के रूप में यहाँ काम की इकाई कॉल कर सकते हैं।
करोल दोबेकई

लेकिन ऐसा केवल सीमा के साथ ही क्यों होता है?
थॉमस अहले

@ThomasAhle मैंने अपने दो सेंट के साथ उत्तर संपादित किया।
कोस्टी सियुडु

1
@ThomasAhle ने एक ब्रेकपॉइंट सेट Integer.sum()किया, जिसका उपयोग IntStream.sumreducer द्वारा किया गया । आप देखेंगे कि नो-लिमिट संस्करण हर समय कार्य करता है, जबकि सीमित संस्करण को OOM से पहले कभी भी कॉल नहीं किया जाता है।
कोस्टी सियादुतु
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.