जावा मल्टी-कोर सिस्टम का लाभ उठाने के लिए स्ट्रीम ऑपरेशंस को समानांतर कर सकता है। यह लेख एक परिप्रेक्ष्य प्रदान करता है और दिखाता है कि कैसे समानांतर धारा उपयुक्त उदाहरणों के साथ प्रदर्शन में सुधार कर सकती है।
जावा में स्ट्रीम
एक स्ट्रीम जावा में डेटा की एक नाली के रूप में प्रदर्शित वस्तुओं का एक क्रम है। इसका आमतौर पर एक स्रोत . होता है जहां डेटा स्थित है और एक गंतव्य जहां इसे प्रसारित किया जाता है। ध्यान दें कि एक धारा एक भंडार नहीं है; इसके बजाय, यह किसी डेटा स्रोत जैसे किसी सरणी या संग्रह पर संचालित होता है। पैसेज में बीच के बिट्स को वास्तव में स्ट्रीम कहा जाता है। संचरण की प्रक्रिया के दौरान, धारा आमतौर पर एक या एक से अधिक संभावित परिवर्तनों से गुजरती है, जैसे फ़िल्टरिंग या सॉर्टिंग, या यह डेटा पर चलने वाली कोई अन्य प्रक्रिया हो सकती है। यह मूल डेटा को एक अलग रूप में अनुकूलित करता है, आमतौर पर, प्रोग्रामर की आवश्यकता के अनुसार। इसलिए, उस पर लागू ऑपरेशन के अनुसार एक नई धारा बनाई जाती है। उदाहरण के लिए, जब किसी स्ट्रीम को सॉर्ट किया जाता है, तो इसका परिणाम एक नई स्ट्रीम में होता है जो एक परिणाम उत्पन्न करता है जिसे फिर सॉर्ट किया जाता है। इसका मतलब है कि नया डेटा मूल रूप में होने के बजाय मूल की एक रूपांतरित प्रति है।
अनुक्रमिक स्ट्रीम
जावा में कोई भी स्ट्रीम ऑपरेशन, जब तक कि स्पष्ट रूप से समानांतर के रूप में निर्दिष्ट नहीं किया जाता है, क्रमिक रूप से संसाधित किया जाता है। वे मूल रूप से गैर-समानांतर धाराएं हैं जिनका उपयोग उनकी पाइपलाइन को संसाधित करने के लिए एक ही धागे में किया जाता है। अनुक्रमिक धाराएँ कभी भी मल्टीकोर सिस्टम का लाभ नहीं उठाती हैं, भले ही अंतर्निहित सिस्टम समानांतर निष्पादन का समर्थन कर सकता है। क्या होता है, उदाहरण के लिए, जब हम स्ट्रीम को प्रोसेस करने के लिए मल्टीथ्रेडिंग लागू करते हैं? फिर भी, यह एक समय में एक ही कोर पर काम करता है। हालांकि, यह एक कोर से दूसरे कोर तक कूद सकता है जब तक कि स्पष्ट रूप से किसी विशिष्ट कोर पर पिन नहीं किया जाता है। उदाहरण के लिए, चार अलग-अलग धागे बनाम चार अलग-अलग कोर में प्रसंस्करण स्पष्ट रूप से अलग है जहां पूर्व का बाद के साथ कोई मेल नहीं है। एकल कोर वातावरण में कई थ्रेड्स को निष्पादित करना काफी संभव है, लेकिन समानांतर प्रसंस्करण पूरी तरह से एक अलग शैली है। एक प्रोग्राम को ऐसे वातावरण में क्रियान्वित करने के अलावा समानांतर प्रोग्रामिंग के लिए तैयार किया जाना चाहिए जो इसका समर्थन करता है। यही कारण है कि समानांतर प्रोग्रामिंग एक जटिल क्षेत्र है।
आइए इस विचार को और स्पष्ट करने के लिए एक उदाहरण का प्रयास करें।
package org.mano.example; import java.util.Arrays; import java.util.List; public class Main2 { public static oid main(String[] args) { List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8,9); list.stream().forEach(System.out::println); System.out.println(); list.parallelStream().forEach(System.out::println); } }
आउटपुट
123456789 685973214
यह उदाहरण q अनुक्रमिक धारा के साथ-साथ संचालन में q समानांतर धारा का एक उदाहरण है। list.stream() println() . के साथ एक ही थ्रेड पर क्रम से काम करता है कार्यवाही। list.parallelStream() दूसरी ओर, अंतर्निहित मल्टीकोर वातावरण का पूरा लाभ उठाते हुए, समानांतर में संसाधित किया जाता है। दिलचस्प पहलू पिछले कार्यक्रम के आउटपुट में है। अनुक्रमिक स्ट्रीम के मामले में, सूची की सामग्री को क्रमबद्ध क्रम में मुद्रित किया जाता है। दूसरी ओर, समानांतर स्ट्रीम का आउटपुट अनियंत्रित होता है और हर बार प्रोग्राम चलाने पर क्रम बदल जाता है। यह कम से कम एक बात का संकेत देता है:list.parallelStream() . का आह्वान विधि println . बनाती है स्टेटमेंट कई थ्रेड्स में काम करता है, कुछ ऐसा जो list.stream() एक ही सूत्र में करता है।
समानांतर स्ट्रीम
समानांतर स्ट्रीम का उपयोग करने के पीछे प्राथमिक प्रेरणा स्ट्रीम प्रोसेसिंग को समानांतर प्रोग्रामिंग का एक हिस्सा बनाना है, भले ही पूरा प्रोग्राम समानांतर न हो। समानांतर स्ट्रीम मल्टीकोर प्रोसेसर का लाभ उठाती है, जिसके परिणामस्वरूप प्रदर्शन में पर्याप्त वृद्धि होती है। किसी भी समानांतर प्रोग्रामिंग के विपरीत, वे जटिल और त्रुटि प्रवण हैं। हालांकि, जावा स्ट्रीम लाइब्रेरी इसे आसानी से और विश्वसनीय तरीके से करने की क्षमता प्रदान करती है। पूरे कार्यक्रम को समानांतर नहीं किया जा सकता है। लेकिन कम से कम धारा को संभालने वाले हिस्से को समानांतर किया जा सकता है। वे वास्तव में इस अर्थ में काफी सरल हैं कि हम कुछ तरीकों को अपना सकते हैं और बाकी का ध्यान रखा जाता है। इसे करने के दो तरीके हैं। ऐसा ही एक तरीका है parallelStream() . का उपयोग करके समानांतर स्ट्रीम प्राप्त करना संग्रह . द्वारा परिभाषित विधि . दूसरा तरीका समानांतर () . का आह्वान करना है बेसस्ट्रीम . द्वारा परिभाषित विधि एक क्रमिक धारा पर। अनुक्रमिक धारा आमंत्रण द्वारा समानांतर है। ध्यान दें कि अंतर्निहित प्लेटफ़ॉर्म को समानांतर प्रोग्रामिंग का समर्थन करना चाहिए, जैसे कि मल्टीकोर सिस्टम के साथ। अन्यथा, आह्वान का कोई मतलब नहीं है। ऐसे मामले में धारा को क्रम से संसाधित किया जाएगा, भले ही हमने आह्वान किया हो। यदि आह्वान पहले से ही समानांतर धारा पर किया गया है, तो यह कुछ नहीं करता है और केवल धारा लौटाता है।
यह सुनिश्चित करने के लिए कि धारा पर लागू समानांतर प्रसंस्करण का परिणाम अनुक्रमिक प्रसंस्करण के माध्यम से प्राप्त होता है, समानांतर धाराएं स्टेटलेस, गैर-हस्तक्षेप और सहयोगी होना चाहिए।
एक त्वरित उदाहरण
package org.mano.example; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Employee> employees = Arrays.asList( new Employee(1276, "FFF",2000.00), new Employee(7865, "AAA",1200.00), new Employee(4975, "DDD",3000.00), new Employee(4499, "CCC",1500.00), new Employee(9937, "GGG",2800.00), new Employee(5634, "HHH",1100.00), new Employee(9276, "BBB",3200.00), new Employee(6852, "EEE",3400.00)); System.out.println("Original List"); printList(employees); // Using sequential stream long start = System.currentTimeMillis(); List<Employee> sortedItems = employees.stream() .sorted(Comparator .comparing(Employee::getName)) .collect(Collectors.toList()); long end = System.currentTimeMillis(); System.out.println("sorted using sequential stream"); printList(sortedItems); System.out.println("Total the time taken process :" + (end - start) + " milisec."); // Using parallel stream start = System.currentTimeMillis(); List<Employee> anotherSortedItems = employees .parallelStream().sorted(Comparator .comparing(Employee::getName)) .collect(Collectors.toList()); end = System.currentTimeMillis(); System.out.println("sorted using parallel stream"); printList(anotherSortedItems); System.out.println("Total the time taken process :" + (end - start) + " milisec."); double totsal=employees.parallelStream() .map(e->e.getSalary()) .reduce(0.00,(a1,a2)->a1+a2); System.out.println("Total Salary expense: "+totsal); Optional<Employee> maxSal=employees.parallelStream() .reduce((Employee e1, Employee e2)-> e1.getSalary()<e2.getSalary()?e2:e1); if(maxSal.isPresent()) System.out.println(maxSal.get().toString()); } public static void printList(List<Employee> list) { for (Employee e : list) System.out.println(e.toString()); } } package org.mano.example; public class Employee { private int empid; private String name; private double salary; public Employee() { super(); } public Employee(int empid, String name, double salary) { super(); this.empid = empid; this.name = name; this.salary = salary; } public int getEmpid() { return empid; } public void setEmpid(int empid) { this.empid = empid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } @Override public String toString() { return "Employee [empid=" + empid + ", name=" + name + ", salary=" + salary + "]"; } }
पिछले कोड में, ध्यान दें कि अनुक्रमिक निष्पादन का उपयोग करके हमने स्ट्रीम एक पर सॉर्टिंग कैसे लागू की है।
List<Employee> sortedItems = employees.stream() .sorted(Comparator .comparing(Employee::getName)) .collect(Collectors.toList());
और कोड को थोड़ा बदलकर समानांतर निष्पादन प्राप्त किया जाता है।
List<Employee> anotherSortedItems = employees .parallelStream().sorted(Comparator .comparing(Employee::getName)) .collect(Collectors.toList());
हम यह जानने के लिए सिस्टम समय की तुलना भी करेंगे कि कोड के किस भाग में अधिक समय लगता है। समानांतर संचालन तब शुरू होता है जब समानांतर धारा स्पष्ट रूप से parallelStream() . द्वारा प्राप्त की जाती है तरीका। एक और दिलचस्प तरीका है, जिसे reduce() . कहा जाता है . जब हम इस विधि को समानांतर धारा में लागू करते हैं, तो ऑपरेशन विभिन्न थ्रेड्स में हो सकता है।
हालाँकि, हम हमेशा जरूरत के अनुसार समानांतर और अनुक्रमिक के बीच स्विच कर सकते हैं। यदि हम समानांतर धारा को अनुक्रमिक में बदलना चाहते हैं, तो हम अनुक्रमिक () को लागू करके ऐसा कर सकते हैं। बेसस्ट्रीम . द्वारा निर्दिष्ट विधि . जैसा कि हमने अपने पहले कार्यक्रम में देखा, तत्वों के क्रम के अनुसार स्ट्रीम पर किए गए ऑपरेशन को ऑर्डर या अनॉर्डर्ड किया जा सकता है। इसका मतलब है कि ऑर्डर डेटा स्रोत पर निर्भर करता है। हालांकि, समानांतर धाराओं के मामले में यह स्थिति नहीं है। प्रदर्शन को बढ़ावा देने के लिए, उन्हें समानांतर में संसाधित किया जाता है। क्योंकि यह बिना किसी क्रम के किया जाता है, जहां धारा के प्रत्येक विभाजन को बिना किसी समन्वय के अन्य विभाजनों से स्वतंत्र रूप से संसाधित किया जाता है, परिणाम अप्रत्याशित रूप से अनियंत्रित होता है। लेकिन, अगर हम ऑर्डर करने के लिए समानांतर स्ट्रीम में प्रत्येक तत्व पर विशेष रूप से एक ऑपरेशन करना चाहते हैं, तो हम forEachOrdered() पर विचार कर सकते हैं। विधि, जो forEach() . का एक विकल्प है विधि।
निष्कर्ष
स्ट्रीम एपीआई लंबे समय से जावा का हिस्सा रहे हैं, लेकिन समानांतर प्रसंस्करण के ट्वीक को जोड़ना बहुत स्वागत योग्य है, और साथ ही साथ काफी दिलचस्प विशेषता है। यह विशेष रूप से सच है क्योंकि आधुनिक मशीनें मल्टीकोर हैं और एक कलंक है कि समानांतर प्रोग्रामिंग डिजाइन जटिल है। जावा द्वारा प्रदान किए गए एपीआई एक जावा प्रोग्राम में समानांतर प्रोग्रामिंग ट्वीक के एक टिंग को शामिल करने की क्षमता प्रदान करते हैं जिसमें अनुक्रमिक निष्पादन का समग्र डिज़ाइन होता है। यह शायद इस सुविधा का सबसे अच्छा हिस्सा है।