2016-06-29 15 views
10

चूंकि वह जावा 8 की शुरूआत के बाद से मुझे वास्तव में भेड़ के बच्चे के साथ लगाया गया और जब भी संभव हो, उनका उपयोग शुरू कर दिया, ज्यादातर उन्हें आदी हो जाना शुरू कर दिया। सबसे आम उपयोग में से एक है जब हम वस्तुओं के संग्रह पर पुन: प्रयास करना और कार्य करना चाहते हैं, इस मामले में मैं या तो forEach या stream() का सहारा लेता हूं। मैं शायद ही कभी पुराना for(T t : Ts) पाश लिखता हूं और मैं लगभग for(int i = 0.....) के बारे में भूल गया था।लैम्ब्डा पुनरावृत्ति और सामान्य पाश के बीच कैसे निर्णय लेना है?

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

पीएस: मैं उन मामलों के बारे में बात कर रहा हूं जहां parallelStream लागू करने की अनुशंसा की जाती है। जाहिर है कि वे तेज़ होंगे।

+5

सीपीयू reqs और/या स्ट्रीम आकार बड़ा नहीं है, तो समानांतर धारा सामान्य लूप से धीमी हो सकती है। प्रबंधन के लिए धागे महंगा हैं। – Bohemian

+1

स्ट्रीम ढांचे का ओवरहेड आश्चर्यजनक हो सकता है। उपाय! –

+0

वास्तव में और इस प्रकार मैंने अपना प्रश्न अपडेट किया। – Konstantine

उत्तर

1

यह विशिष्ट कार्यान्वयन पर निर्भर करता है।

सामान्य forEach विधि और foreachIterator पर लूप आमतौर पर समान समान प्रदर्शन करते हैं क्योंकि वे समान स्तर के अमूर्तता का उपयोग करते हैं। stream() आमतौर पर धीमा होता है (अक्सर 50-70% तक) क्योंकि यह एक और स्तर जोड़ता है जो अंतर्निहित संग्रह तक पहुंच प्रदान करता है।

stream() के फायदे आमतौर पर जेडीके द्वारा प्रदान किए जाने वाले बहुत से पुन: प्रयोज्य लोगों के साथ संभावित समांतरता और संचालन की आसान श्रृंखला हैं।

+1

बिंदु पर, इसलिए 'List.forEach' कोई प्रदर्शन जुर्माना नहीं देता है, लेकिन दुर्भाग्य से' List.stream /() .Each' की अभिव्यक्तिपूर्ण शक्ति नहीं है। –

+1

मैं 50-70% धीमी धाराओं के बयान से सहमत नहीं हूं। ज्यादातर मामलों में, ये संख्याएं पहली बार ओवरहेड मापने वाले गलत बेंचमार्क से होती हैं। – Holger

2

प्रदर्शन इतने सारे कारकों पर निर्भर करता है कि यह भविष्यवाणी करना मुश्किल है। आम तौर पर, हम कहेंगे, अगर आपका पर्यवेक्षक दावा करता है कि प्रदर्शन के साथ कोई समस्या थी, तो आपका पर्यवेक्षक को समझने का प्रभारी है क्या समस्या है।

एक चीज किसी से डर सकती है, दृश्यों के पीछे, प्रत्येक लैम्ब्डा निर्माण स्थल (वर्तमान कार्यान्वयन के साथ) के लिए एक वर्ग उत्पन्न होता है, इसलिए यदि प्रश्न में कोड केवल एक बार निष्पादित किया जाता है, तो इसे एक माना जा सकता है संसाधनों का अपशिष्ट यह इस तथ्य के साथ सामंजस्यपूर्ण है कि लैम्ब्डा अभिव्यक्तियों में सामान्य अनिवार्य कोड (हम यहां आंतरिक कक्षाओं की तुलना नहीं कर रहे हैं) के रूप में उच्च प्रारंभिक ओवरहेड होते हैं, इसलिए क्लास प्रारंभकर्ताओं के अंदर, जो केवल एक बार चलते हैं, आप इसे टालने पर विचार कर सकते हैं। यह इस तथ्य के अनुरूप भी है कि आपको never use parallel streams in class initializers होना चाहिए, इसलिए यह संभावित लाभ वैसे भी उपलब्ध नहीं है।

साधारण के लिए, अक्सर मार डाला कोड है कि JVM द्वारा अनुकूलित किया जा सकता है, इन समस्याओं उठता नहीं है। जैसा कि आपने सही ढंग से माना है, लैम्ब्डा अभिव्यक्तियों के लिए उत्पन्न कक्षाएं अन्य वर्गों के समान उपचार (अनुकूलन) प्राप्त करती हैं। इन स्थानों पर, संग्रह पर forEach बुला अधिक कुशल एक for पाश से किया जा रहा है की क्षमता होती है।

अस्थायी वस्तु एक Iterator या लैम्ब्डा अभिव्यक्ति के लिए बनाया उदाहरणों नगण्य हैं, तथापि, यह ध्यान देने योग्य है कि एक foreach पाश हमेशा जबकि lambda expression do not always do एक Iterator उदाहरण पैदा करेगा हो सकता है। Iterable.forEach की default कार्यान्वयन के साथ-साथ एक Iterator पैदा करेगा, वहीं सबसे अक्सर इस्तेमाल किया संग्रह में से कुछ एक विशेष कार्यान्वयन प्रदान करने का अवसर, सबसे विशेष रूप ArrayList ले।

ArrayList का forEach मूल रूप से Iterator के बिना एक सरणी पर for लूप है। इसके बाद की accept विधि का आह्वान किया जाएगा, जो आपके द्वारा लैम्ब्डा अभिव्यक्ति के कोड वाले सिंथेटिक विधि में एक छोटा प्रतिनिधिमंडल वाला एक जेनरेट क्लास होगा। पूरे लूप को अनुकूलित करने के लिए, ऑप्टिमाइज़र के क्षितिज को एक सरणी (एक ऑप्टिमाइज़र के लिए पहचानने योग्य एक सामान्य मुहावरे) पर ArrayList के लूप को विस्तारित करना है, सिंथेटिक accept विधि जिसमें एक छोटा प्रतिनिधिमंडल और आपके वास्तविक कोड वाली विधि शामिल है।

इसके विपरीत, जब एक ही सूची से अधिक पुनरावृत्ति एक foreach पाश का उपयोग कर, एक Iterator कार्यान्वयन ArrayList यात्रा तर्क, दो तरीकों, Iterator की hasNext() और next() और उदाहरण चर में फैला युक्त बनाया जाता है। पाश बार-बार, अंत हालत (index<size) और next() जो तत्व लौटने से पहले पुनः जाँच करेगा स्थिति की जांच करने के लिए hasNext() विधि लागू करेगा के रूप में वहाँ कोई गारंटी है कि फोन करने वाले ठीक से next() से पहले hasNext() आह्वान करता है। बेशक, एक अनुकूलक इस डुप्लिकेशंस को हटाने में सक्षम है, लेकिन इसे पहले स्थान पर रखने की अपेक्षा अधिक प्रयास की आवश्यकता नहीं है। तो forEach विधि के समान प्रदर्शन प्राप्त करने के लिए, ऑप्टिमाइज़र के क्षितिज को आपके लूप कोड, नॉनट्रिविअल hasNext() कार्यान्वयन और नॉनट्रिविअल next() कार्यान्वयन को विस्तारित करना होगा।

इसी तरह की चीजें अन्य संग्रहों पर भी लागू हो सकती हैं जिनमें विशेष forEach कार्यान्वयन भी है। यह स्ट्रीम ऑपरेशंस पर भी लागू होता है, यदि स्रोत एक विशेष Spliterator कार्यान्वयन प्रदान करता है, जो Iterator जैसे दो तरीकों से पुनरावृत्ति तर्क फैलता नहीं है।

तो यदि आप for के प्रत्येक तकनीकी बनाम forEach(…) के तकनीकी पहलुओं पर चर्चा करना चाहते हैं, तो आप इन जानकारी का उपयोग कर सकते हैं।

लेकिन जैसा कि कहा गया है, इन पहलुओं में केवल संभावित प्रदर्शन पहलुओं का वर्णन किया गया है क्योंकि अनुकूलक के काम और अन्य रनटाइम पर्यावरणीय पहलू पूरी तरह से परिणाम बदल सकते हैं। मुझे लगता है, अंगूठे के नियम के रूप में, लूप शरीर/क्रिया छोटा है, forEach विधि अधिक उपयुक्त है। यह वैसे भी अत्यधिक लंबे लैम्ब्डा अभिव्यक्तियों से परहेज करने के दिशानिर्देश के साथ पूरी तरह से सामंजस्य बनाता है।

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