क्या मुझे हमेशा संभव होने पर एक समानांतर धारा का उपयोग करना चाहिए?


514

जावा 8 और लैम्ब्डा के साथ धाराओं के रूप में संग्रह पर पुनरावृति करना आसान है, और बस एक समानांतर धारा का उपयोग करना आसान है। डॉक्स से दो उदाहरण , समांतर स्ट्रैस का उपयोग करते हुए दूसरा:

myShapesCollection.stream()
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

myShapesCollection.parallelStream() // <-- This one uses parallel
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

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

क्या अन्य विचार हैं? समानांतर धारा का उपयोग कब किया जाना चाहिए और गैर-समानांतर का उपयोग कब किया जाना चाहिए?

(यह सवाल समानांतर धाराओं का उपयोग कैसे और कब किया जाए, इस बारे में चर्चा शुरू करने के लिए कहा गया है, इसलिए नहीं कि मुझे लगता है कि हमेशा उनका उपयोग करना एक अच्छा विचार है।)

जवाबों:


735

अनुक्रमिक एक की तुलना में एक समानांतर धारा में बहुत अधिक ओवरहेड होता है। थ्रेड्स को समन्वित करने में महत्वपूर्ण समय लगता है। मैं डिफ़ॉल्ट रूप से अनुक्रमिक धाराओं का उपयोग करूंगा और केवल समानांतर वाले पर विचार करूंगा यदि

  • मेरे पास संसाधित करने के लिए बड़ी मात्रा में आइटम हैं (या प्रत्येक आइटम के प्रसंस्करण में समय लगता है और समानांतर होता है)

  • मुझे पहली बार एक प्रदर्शन समस्या है

  • मैं पहले से ही बहु-थ्रेड वातावरण में प्रक्रिया नहीं चलाता (उदाहरण के लिए: एक वेब कंटेनर में, अगर मेरे पास पहले से ही समानांतर में प्रक्रिया करने के लिए कई अनुरोध हैं, तो प्रत्येक अनुरोध के अंदर समानता की एक अतिरिक्त परत जोड़ने से सकारात्मक प्रभाव की तुलना में अधिक नकारात्मक हो सकता है। )

आपके उदाहरण में, प्रदर्शन को वैसे भी सिंक्रनाइज़ एक्सेस द्वारा संचालित किया जाएगा System.out.println() , और इस प्रक्रिया को समानांतर बनाने से कोई प्रभाव नहीं होगा, या यहां तक ​​कि एक नकारात्मक भी।

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

किसी भी मामले में, उपाय, अनुमान मत करो! केवल एक माप आपको बताएगा कि समानांतरवाद इसके लायक है या नहीं।


18
अच्छा उत्तर। मुझे लगता है कि यदि आपके पास संसाधित करने के लिए बड़ी मात्रा में आइटम हैं, तो यह केवल थ्रेड समन्वय मुद्दों को बढ़ाता है; यह केवल तब होता है जब प्रत्येक आइटम के प्रसंस्करण में समय लगता है और यह समानांतर होता है कि समानांतरकरण उपयोगी हो सकता है।
वारेन ड्यू

16
@WarrenDew मैं असहमत हूं। फोर्क / जॉइन सिस्टम केवल N आइटम को उदाहरण के लिए, 4 भागों में विभाजित करेगा, और इन 4 भागों को क्रमिक रूप से प्रोसेस करेगा। फिर 4 परिणाम कम हो जाएंगे। यदि वास्तव में बड़े पैमाने पर बड़े पैमाने पर है, तो भी तेजी से इकाई प्रसंस्करण के लिए, समानांतरकरण प्रभावी हो सकता है। लेकिन हमेशा की तरह, आपको मापना होगा।
जेबी निज़ैट

मेरे पास उन वस्तुओं का एक संग्रह है जो लागू करते हैं Runnableकि मैं start()उन्हें उपयोग करने के लिए कहता हूं Threads, क्या यह बदलना ठीक है कि एक .forEach()समानांतर में जावा 8 धाराओं का उपयोग करें ? तब मैं थ्रेड कोड को कक्षा से बाहर करने में सक्षम हो जाऊंगा। लेकिन क्या कोई डाउनसाइड है?
यम

1
@ जेबीएनज़ेट यदि 4 भागों को क्रमिक रूप से पोक करता है, तो इसके प्रक्रिया समानताएं या क्रमिक रूप से ज्ञात होने का कोई अंतर नहीं है? Pls स्पष्ट करें
हर्षाना

3
@ हर्षाना का स्पष्ट रूप से मतलब है कि 4 भागों में से प्रत्येक के तत्वों को क्रमिक रूप से संसाधित किया जाएगा। हालांकि, भागों को स्वयं एक साथ संसाधित किया जा सकता है। दूसरे शब्दों में, यदि आपके पास कई सीपीयू कोर उपलब्ध हैं, तो प्रत्येक भाग अपने स्वयं के तत्वों को स्वतंत्र रूप से दूसरे भागों में चला सकता है, जबकि अपने स्वयं के तत्वों को क्रमिक रूप से संसाधित कर सकता है। (नोट: मुझे नहीं पता, अगर यह कैसे समानांतर जावा स्ट्रीम काम करता है, मैं सिर्फ यह स्पष्ट करने की कोशिश कर रहा हूं कि जेबीएनज़ेट का क्या मतलब है।)
कल

258

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

हालांकि, सिर्फ इसलिए कि यह आसान है, इसका मतलब यह नहीं है कि यह हमेशा एक अच्छा विचार है, और वास्तव में, यह एक बुरा विचार है .parallel()कि आप सभी जगह सिर्फ इसलिए छोड़ सकते हैं क्योंकि आप कर सकते हैं।

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

इसके अलावा, ध्यान दें कि समानता भी अक्सर संगणनात्मक कार्यान्वयन द्वारा छिपी हुई संगणना में nondeterminism को उजागर करती है; कभी-कभी यह कोई मायने नहीं रखता है, या इसमें शामिल ऑपरेशनों को बाध्य करके इसे कम किया जा सकता है (यानी, कमी ऑपरेटरों को निष्क्रिय और साहचर्य होना चाहिए)।

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

(ए) आप जानते हैं कि वास्तव में प्रदर्शन को बढ़ाने के लिए लाभ है और

(बी) कि यह वास्तव में बढ़ा हुआ प्रदर्शन प्रदान करेगा।

(ए) एक व्यावसायिक समस्या है, न कि तकनीकी। यदि आप एक प्रदर्शन विशेषज्ञ हैं, तो आप आमतौर पर कोड को देखने और निर्धारित करने में सक्षम होंगे (बी), लेकिन स्मार्ट पथ को मापना है। (और, जब तक आप (ए) के बारे में आश्वस्त न हों, परेशान न हों; यदि कोड पर्याप्त तेज़ है, तो अपने मस्तिष्क चक्रों को कहीं और लगाने के लिए बेहतर है)।

समानता के लिए सबसे सरल प्रदर्शन मॉडल "एनक्यू" मॉडल है, जहां एन तत्वों की संख्या है, और क्यू प्रत्येक तत्व की गणना है। सामान्य तौर पर, प्रदर्शन लाभ प्राप्त करने से पहले आपको उत्पाद NQ को कुछ सीमा से अधिक करने की आवश्यकता होती है। कम क्यू समस्या के लिए जैसे "1 से एन तक संख्याएं जोड़ें", आप आमतौर पर एन = 1000 और एन = 10000 के बीच एक विचलन देखेंगे। उच्च-क्यू समस्याओं के साथ, आप निचले थ्रेसहोल्ड पर ब्रेकेवेंस देखेंगे।

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


18
यह पोस्ट NQ मॉडल के बारे में अधिक जानकारी देता है: gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html
Pino

4
@specializt: समानांतर करने के लिए अनुक्रमिक से एक धारा स्विचन करता है एल्गोरिथ्म (ज्यादातर मामलों में) बदल जाते हैं। यहाँ निर्धारित नियतिवाद आपके (मनमाने ढंग से) संचालकों के बारे में है, जिन पर ऑपरेटर भरोसा कर सकते हैं (स्ट्रीम कार्यान्वयन यह नहीं जान सकता है), लेकिन निश्चित रूप से इस पर भरोसा करना चाहिए । इस जवाब के उस हिस्से ने यही कहने की कोशिश की। यदि आप नियमों की परवाह करते हैं, तो आप एक नियतात्मक परिणाम रख सकते हैं, जैसे आप कहते हैं, (अन्यथा समानांतर धाराएं काफी बेकार थीं), लेकिन जानबूझकर गैर-नियतात्मकता की भी संभावना है, जैसे कि … के findAnyबजाय उपयोग करते समयfindFirst
होल्गर

4
"पहले, ध्यान दें कि जब अधिक कोर उपलब्ध होते हैं तो समानांतर निष्पादन तेजी से निष्पादन की संभावना के अलावा कोई लाभ नहीं देता है" - या यदि आप एक कार्रवाई लागू कर रहे हैं जिसमें IO (जैसे myListOfURLs.stream().map((url) -> downloadPage(url))...) शामिल हैं।
जूल्स

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

2
@ जूल्स: आईओ के लिए कभी भी समानांतर धाराओं का उपयोग न करें। वे पूरी तरह से सीपीयू गहन संचालन के लिए हैं। समानांतर धाराएं उपयोग करती हैं ForkJoinPool.commonPool()और आप वहां जाने के लिए कार्यों को अवरुद्ध नहीं करना चाहते हैं।
R2C2

68

मैंने ब्रायन गोएत्ज़ (जावा लैंग्वेज आर्किटेक्ट और लैम्बडा एक्सप्रेशंस के लिए विनिर्देशन नेतृत्व) की प्रस्तुतियों में से एक को देखा । वह समानांतरकरण के लिए जाने से पहले निम्नलिखित 4 बिंदुओं पर विस्तार से बताते हैं:

विभाजन / अपघटन लागत
- कभी-कभी विभाजन केवल कार्य करने से अधिक महंगा होता है!
कार्य प्रेषण / प्रबंधन लागत
- उस समय में बहुत से काम कर सकता है जो किसी अन्य धागे को काम सौंपने में करता है।
परिणाम संयोजन लागत
- कभी-कभी संयोजन में बहुत सारे डेटा की प्रतिलिपि शामिल होती है। उदाहरण के लिए, संख्याओं को जोड़ना सस्ता है जबकि विलय सेट महंगा है।
स्थानीयता
- कमरे में हाथी। यह एक महत्वपूर्ण बिंदु है जिसे हर कोई याद कर सकता है। आपको कैश मिस पर विचार करना चाहिए, अगर कोई सीपीयू कैशे मिस होने के कारण डेटा का इंतजार करता है तो आप समानांतर रूप से कुछ भी हासिल नहीं करेंगे। यही कारण है कि सरणी आधारित स्रोत अगले सूचकांकों के रूप में सबसे अच्छा समानांतर करते हैं (वर्तमान सूचकांक के पास) कैश किए जाते हैं और कम संभावनाएं हैं कि सीपीयू कैश मिस का अनुभव करेगा।

उन्होंने समानांतर गति की संभावना निर्धारित करने के लिए अपेक्षाकृत सरल सूत्र का भी उल्लेख किया है।

NQ मॉडल :

N x Q > 10000

जहाँ,
N = डेटा आइटमों की संख्या
Q = प्रति आइटम कार्य की मात्रा


13

जेबी ने सिर पर कील ठोक दी। केवल एक चीज जो मैं जोड़ सकता हूं, वह यह है कि जावा 8 शुद्ध समानांतर प्रसंस्करण नहीं करता है, यह पारायिक करता है । हां, मैंने लेख लिखा है और मैं तीस साल से एफ / जे कर रहा हूं, इसलिए मुझे इस मुद्दे की समझ है।


10
धाराएँ चलने योग्य नहीं हैं क्योंकि धाराएँ बाह्य के बजाय आंतरिक पुनरावृत्ति करती हैं। वैसे भी धाराओं का पूरा कारण यही है। यदि आपको शैक्षणिक कार्यों में कोई समस्या है तो कार्यात्मक प्रोग्रामिंग आपके लिए नहीं हो सकती है। कार्यात्मक प्रोग्रामिंग === गणित === शैक्षणिक। और नहीं, J8-FJ टूटा नहीं है, यह सिर्फ इतना है कि अधिकांश लोग f ****** मैनुअल नहीं पढ़ते हैं। जावा डॉक्स का कहना है कि यह एक समानांतर निष्पादन ढांचा नहीं है। सभी स्प्लिटर सामान की पूरी वजह है। हाँ यह अकादमिक है, हाँ यह काम करता है यदि आप जानते हैं कि इसका उपयोग कैसे करना है। हाँ एक कस्टम निष्पादक का उपयोग करना आसान होना चाहिए
Kr0e

1
स्ट्रीम में एक इटरेटर () विधि होती है, इसलिए यदि आप चाहें तो आप उन्हें बाहरी रूप से प्रसारित कर सकते हैं। मेरी समझ यह थी कि वे Iterable को लागू नहीं करते हैं क्योंकि आप केवल उस पुनरावृत्ति का उपयोग एक बार कर सकते हैं और कोई भी यह तय नहीं कर सकता कि क्या यह ठीक था।
तर्जुक

14
ईमानदार होने के लिए: आपका पूरा पेपर एक विशाल, विस्तृत शेख़ी की तरह पढ़ता है - और यह बहुत ज्यादा इसकी विश्वसनीयता को नकारता है ... मैं इसे बहुत कम आक्रामक उपक्रम के साथ फिर से करने की सलाह दूंगा अन्यथा बहुत से लोग वास्तव में इसे पूरी तरह से पढ़ने के लिए परेशान नहीं करेंगे ... im सिर्फ Sayan
specializt

आपके लेख के बारे में सवालों की एक जोड़ी ... सबसे पहले, आप स्पष्ट रूप से निर्देशित एसाइक्लिक ग्राफ के साथ संतुलित वृक्ष संरचनाओं की बराबरी क्यों करते हैं? हाँ, संतुलित पेड़ हैं जुड़ा हुआ सूचियों कर रहे हैं और काफी हर वस्तु उन्मुख डेटा संरचना सरणियों के अलावा अन्य DAGs, लेकिन इतना। इसके अलावा, जब आप कहते हैं कि पुनरावर्ती अपघटन केवल संतुलित वृक्ष संरचनाओं पर काम करता है और इसलिए व्यावसायिक रूप से प्रासंगिक नहीं है, तो आप उस दावे को कैसे सही ठहराते हैं? यह मुझे लगता है (वास्तव में गहराई से इस मुद्दे की जांच किए बिना) कि यह सिर्फ सरणी-आधारित डेटास्ट्रक्चर, जैसे ArrayList/ पर काम करना चाहिए HashMap
जूल्स

1
यह धागा 2013 से है, तब से बहुत कुछ बदल गया है। यह खंड उन टिप्पणियों के लिए है जिनका विस्तृत उत्तर नहीं है।
edharned

3

अन्य उत्तरों ने पहले से ही समानांतर प्रसंस्करण में समय से पहले अनुकूलन और ओवरहेड लागत से बचने के लिए प्रोफाइलिंग को कवर किया है। यह उत्तर समानांतर स्ट्रीमिंग के लिए डेटा संरचनाओं के आदर्श विकल्प की व्याख्या करता है।

एक नियम के रूप में, समानांतरवाद से निष्पादन लाभ से अधिक धाराओं पर सबसे अच्छा कर रहे हैं ArrayList, HashMap, HashSet, और ConcurrentHashMapउदाहरण; सरणियों; intपर्वतमाला; और longपर्वतमाला। आम तौर पर इन डेटा संरचनाओं में यह है कि वे सभी सटीक रूप से और सस्ते में किसी भी वांछित आकारों के सबरेंज में विभाजित हो सकते हैं, जिससे समानांतर धागे के बीच काम को विभाजित करना आसान हो जाता है। इस कार्य को करने के लिए स्ट्रीम लाइब्रेरी द्वारा उपयोग किया जाने वाला एब्स्ट्रैक्शन स्प्लिटेटर है, जिसे spliteratorविधि द्वारा Streamऔर पर लौटाया जाता है Iterable

एक अन्य महत्वपूर्ण कारक जो इन सभी डेटा संरचनाओं में समान है, वे क्रमिक रूप से संसाधित होने पर संदर्भ के अच्छे-से-उत्कृष्ट इलाके प्रदान करते हैं: अनुक्रमिक तत्व संदर्भ स्मृति में एक साथ संग्रहीत होते हैं। उन संदर्भों द्वारा संदर्भित वस्तुएं स्मृति में एक दूसरे के करीब नहीं हो सकती हैं, जो स्थानीयता-संदर्भ को कम करती हैं। बल्क ऑपरेशन को समानांतर करने के लिए स्थानीयता-संदर्भ महत्वपूर्ण रूप से महत्वपूर्ण हो जाता है: इसके बिना, थ्रेड अपना अधिकांश समय बेकार में व्यतीत करते हैं, डेटा के प्रोसेसर की मेमोरी में मेमोरी से स्थानांतरित होने की प्रतीक्षा करते हैं। संदर्भ के सर्वश्रेष्ठ इलाके के साथ डेटा संरचनाएँ आदिम सरणियाँ हैं क्योंकि डेटा को स्मृति में संचित रूप से संग्रहीत किया जाता है।

स्रोत: आइटम # 48 जोशुआ बलोच द्वारा धाराओं को समानांतर बनाते समय सावधानी बरतें, प्रभावी जावा 3 ई


2

कभी भी एक सीमा के साथ अनंत धारा को समानांतर न करें। यहाँ होता है:

    public static void main(String[] args) {
        // let's count to 1 in parallel
        System.out.println(
            IntStream.iterate(0, i -> i + 1)
                .parallel()
                .skip(1)
                .findFirst()
                .getAsInt());
    }

परिणाम

    Exception in thread "main" java.lang.OutOfMemoryError
        at ...
        at java.base/java.util.stream.IntPipeline.findFirst(IntPipeline.java:528)
        at InfiniteTest.main(InfiniteTest.java:24)
    Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.base/java.util.stream.SpinedBuffer$OfInt.newArray(SpinedBuffer.java:750)
        at ...

यदि आप उपयोग करते हैं तो भी .limit(...)

यहाँ स्पष्टीकरण: जावा 8, का उपयोग कर। एक धारा में समानांतर OOM त्रुटि का कारण बनता है

इसी तरह, यदि धारा का आदेश दिया गया है तो समानांतर का उपयोग न करें और आपके द्वारा संसाधित किए जाने की तुलना में बहुत अधिक तत्व हैं, जैसे

public static void main(String[] args) {
    // let's count to 1 in parallel
    System.out.println(
            IntStream.range(1, 1000_000_000)
                    .parallel()
                    .skip(100)
                    .findFirst()
                    .getAsInt());
}

यह बहुत अधिक समय तक चल सकता है क्योंकि समानांतर धागे महत्वपूर्ण 0- 0- के बजाय संख्या की सीमा पर बहुत अधिक काम कर सकते हैं, जिससे इसे बहुत लंबा समय लग सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.