متى تفيد المعالجة المتوازية
عوامل عبء العمل وحجم البيانات
متى تفيد المعالجة المتوازية درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.
للتوازي تكلفة
يضيف التنفيذ المتوازي تكلفة إضافية: تقسيم البيانات، وإرسال المهام، ودمج النتائج. ولا يكون مفيدًا إلا عندما تكون هذه التكلفة أقل من الوقت الذي يتم توفيره.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long sum = IntStream.rangeClosed(1, 10_000_000)
.parallel()
.asLongStream()
.sum();
System.out.println(sum);
}
}العامل 1: حجم البيانات (N)
يُوزّع N الكبير التكلفة الثابتة للتوازي على عدد أكبر من العناصر. والقاعدة التقريبية هي أن التوازي يبدأ في أن يصبح مفيدًا عند التعامل مع عشرات الآلاف من العناصر.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long count = IntStream.rangeClosed(1, 5_000_000)
.parallel()
.filter(n -> n % 7 == 0)
.count();
System.out.println(count);
}
}العامل 2: العمل لكل عنصر (Q)
تؤثر تكلفة معالجة كل عنصر، Q، في الأداء. ويستفيد العمل المكثف لكل عنصر (مثل الحسابات الثقيلة) من التوازي حتى عند قيم أصغر لـ N.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long primes = IntStream.rangeClosed(2, 200_000)
.parallel()
.filter(Main::isPrime)
.count();
System.out.println(primes);
}
static boolean isPrime(int n) {
for (int i = 2; (long) i * i <= n; i++)
if (n % i == 0) return false;
return true;
}
}تصوّر N مضروبًا في Q
اعتبر أن إجمالي العمل المفيد يساوي N x Q. كلما زاد هذا الناتج، زادت الفائدة المحتملة من التوازي. ونادرًا ما تبرر قيمة N الصغيرة أو قيمة Q البسيطة استخدامه.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
double sum = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.mapToDouble(n -> Math.sqrt(n) * Math.log(n + 1))
.sum();
System.out.println(sum);
}
}العامل 3: قابلية التقسيم
تتوازى مصادر البيانات التي يمكن تقسيمها بسرعة وبشكل متساوٍ بكفاءة، مثل المصفوفات وArrayList وIntStream.range. أما LinkedList والمصادر المعتمدة على المكررات فتنقسم بشكل سيئ.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) list.add(i);
long even = list.parallelStream().filter(n -> n % 2 == 0).count();
System.out.println(even);
}
}مصدر جيد: المصفوفات والنطاقات
تتميز النطاقات الأولية بحجم معروف، ويمكن تقسيمها في O(1)، مما يجعلها مصادر مثالية للتنفيذ المتوازي.
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) {
long sum = LongStream.rangeClosed(1, 20_000_000)
.parallel()
.sum();
System.out.println(sum);
}
}مصدر ضعيف: المولِّدات التكرارية
ينتج Stream.iterate العناصر بالتسلسل بحكم تعريفه، لذلك لا يمكن تقسيمه قبل إنشاء العناصر. وهو مصدر ضعيف للتنفيذ المتوازي.
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
long count = Stream.iterate(1, n -> n + 1)
.limit(1_000_000)
.parallel()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);
}
}العامل 4: خطوة دمج قليلة التكلفة
يحتاج التوازي إلى طريقة قليلة التكلفة لدمج النتائج الجزئية. إذ يندمج sum وcount بسهولة، بينما يكون دمج قائمة مرتبة أو أداة تجميع تعتمد بكثافة على الأشجار مكلفًا.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int max = IntStream.rangeClosed(1, 5_000_000)
.parallel()
.map(n -> n % 1000)
.max()
.getAsInt();
System.out.println(max);
}
}تجنّبه في العمل المرتبط بالإدخال والإخراج
تستهدف التدفقات المتوازية المهام المرتبطة بالمعالج على مجموعة fork-join. وتؤدي عمليات الإدخال والإخراج الحاجبة إلى استنفاد المجموعة المشتركة، مما يضر بالتطبيق بأكمله. استخدم منفّذات مخصصة للإدخال والإخراج بدلًا من ذلك.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long total = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.mapToLong(n -> (long) n * n)
.sum();
System.out.println(total);
}
}قِس الأداء ولا تخمّن
الطريقة الموثوقة الوحيدة لمعرفة ما إذا كان التوازي مفيدًا هي قياس الأداء باستخدام بيانات واقعية. فكثيرًا ما يكون الحدس المتعلق بالأداء خاطئًا.
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) {
long start = System.nanoTime();
long sum = LongStream.rangeClosed(1, 50_000_000).parallel().sum();
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.println("sum=" + sum + " took ~" + ms + "ms");
}
}قائمة تحقق عملية
فضّل التوازي عند تحقق جميع الشروط التالية:
- قيمة N كبيرة و/أو عمل مكلف لكل عنصر.
- مصدر قابل للتقسيم (مصفوفة أو ArrayList أو نطاق).
- عملية دمج رخيصة وتجميعية.
- عمل مرتبط بالمعالج، من دون عمليات إدخال وإخراج حاجبة.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
double avg = IntStream.rangeClosed(1, 10_000_000)
.parallel()
.mapToDouble(Math::sqrt)
.average()
.getAsDouble();
System.out.println(avg);
}
}تحقق سريع
أي سيناريو هو المرشح الأفضل لاستخدام تدفق متوازٍ؟
مراجعة
لقد تعلمت متى يكون التوازي مفيدًا:
- تزداد الفائدة مع N x Q (حجم البيانات مضروبًا في تكلفة كل عنصر).
- يلزم وجود مصدر قابل للتقسيم (مثل المصفوفات وArrayList والنطاقات).
- يلزم وجود عملية دمج تجميعية قليلة التكلفة.
- حافظ على ارتباط العمل بالمعالج، وتجنب عمليات الإدخال والإخراج الحاجبة، وقِس الأداء دائمًا.
الأسئلة الشائعة
هل درس «متى تفيد المعالجة المتوازية» مجاني؟
نعم — نص درس «متى تفيد المعالجة المتوازية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.
ماذا ستتعلم في «متى تفيد المعالجة المتوازية»؟
عوامل عبء العمل وحجم البيانات تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟
لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «متى تفيد المعالجة المتوازية»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟
نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- إنشاء التدفقات المتوازية
- متى تفيد المعالجة المتوازية
- سلامة الخيوط والآثار الجانبية
- الأخطاء الشائعة