إذا كنت تريد استرجاع معلوماتك عن الـ "البرمجة المتوازية" فهنا مصادر أرشحها لك:
محاضرة متقدمة عن الـ "البرمجة المتوازية" هنا
وهنا مقال.
تأتي لغة الجافا في مقدمة اللغات التي تدعم البرمجة المتوازية؛ حيث أنه عند إطلاق الجافا سنة 1996 تم إطلاقها بتقنيات مزودة داخليا للتعامل مع السبل المتعددة (السبيل هو الـ "thread") مع التزامنية والانتظار\الإشعار.
وعند إصدار جافا 5 قُدمت مع مجموعة التي تساعدك على البرمجة المتوازية مثل (java.util.concurrent) و رزمة الفروع (fork-join).
في جافا 8 تم تزويدها بالـ "استريمز" كخطوة نحو البرمجة الوظيفية، وبما أن الـ "ستريمز" مزودة بخاصية البرمجة المتوازية عن طريق استدعاء واحد لدالة parallel() لمساعدتنا في الحصول على برنامج أسرع وأفضل.
إلا أنه أصبح أصعب من قبل، لإمكانية حدوث كثير من الأشياء غير المتوقعة، لذلك يجب توحي الحذر عن استخدامها.
على سبيل المثال البرنامج التالي والذي يقوم بإيجاد أول 20 عدد أولي:
class Example{
public static void main(String[] args){
primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
.filter(mersenne -> mersenne.isProbalePrime(50))
.limit(20)
.forEach(System.out::println);
}
static Stream<BigInteger> primes(){
return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
}هذا البرنامج قد يأخذ بضع ثواني بدون استخدام ميزة البرمجة المتوازية.
والآن ماذا لو قمنا باستخدام خاصية البرمجة المتوازية هنا؟ هل سيكون أسرع؟ أم أبطئ قليلاً؟
حقيقة هذا البرنامج سيقوم باستخدام موارد المعالج الخاص بك بالكامل وسيدخل في دوامة غير منتهية من العمليات، ولن ينتهي حتى تقوم أنت يدويا بذلك.
ماذا يحصل هنا؟
ببساطة الـ "استريمز" لا تملك أدنى فكرى عن كيفية التعامل مع التوصيلات المتوازية
ببساطة؛ محاولة جعل التوصيلات متوازية لن يزيد من السرعة إذا كان المصدر من نوع الـ "استريم التكراري (Stream.iterate)" أو عند استخدام عملية التحديد (limit) في الـ "استريمز"
يجب أن تتجنب الوصلات(pipeline) هاتين المشكلتين.
الخلاصة: لا تستخدم خاصية التوازي على الوصلات بدون تمييز، السرعة والكفاءة ستكون كارثية!
أفضل استفادة من خاصية التوازي في الـ"استريمز" تكون عند استخدامها مع الـ
ArrayList, HashMap, HashSet and ConcurrentHashMap; arrays; int ranges and long ranges
والمشترك في هذه الأشياء أنه يمكن تقسيمها لنطاقات صغيرة بدقة عالية وبدون تكلفة كبيرة؛
وهناك عامل مشترك آخر في هذه الأشياء إذ يتم تخزين الإشارات للعناصر بطريقة ممتازة عند الوصول لهذه العناصر تسلسلياً؛
استخدام خاصية التوازي على الـ"استريمز" لا يؤدي إلى سرعة وكفاءة ضئيلة فحسب؛ بل يؤدي أيضا إلى نتائج خاطئة وغير متوقعة
عند استخدام خاصية التوازي بالطريقة الصحيحة يكون البرنامج الخاص بنا أكثر كفاءة وسرعة مما كان عليه؛
فعلى سبيل المثال: البرنامج التالي يزيد سرعته بمقدار أربعة أضعاف عند استخدام خاصية التوازي؛
class Example{
// Before it takes about 31 seconds on my machine
static long pi(long n){
return LongStream.rangeClosed(2, n)
.mapToObj(BigInteger::valueOf)
.filter(i -> i.isProbablePrime(50))
.count();
}
// After it takes about 9.2 seconds on my machine
static long pi(long n){
return LongStream.rangeClosed(2, n)
.parallel()
.mapToObj(BigInteger::valueOf)
.filter(i -> i.isProbablePrime(50))
.count();
}
}لا تقوم باستخدام خاصية التوازي على الوصلات إلا عند وجود سبب قوي يدعي إلى ذلك؛ وتأكدك من الإستفادة بها بحيث تزيد السرعة والكفاءة.
لأن تكلفة استخدام خاصية التوازي على الـ"استريمز" بشكل خاطئ قد يؤدي إلى كوارث في السرعة-الكفاءة-النتائج.
لذلك ينبغي عليك التأكد من اأنك تستخدم خاصية التوازي بشكل صحيح.