JavaRush /مدونة جافا /Random-AR /العمليات الموازية على المصفوفات في Java 8 - الترجمة
billybonce
مستوى
Москва

العمليات الموازية على المصفوفات في Java 8 - الترجمة

نشرت في المجموعة
ترجمة المقال
// عمليات المصفوفة المتوازية في Java 8 // بقلم إريك برونو، 25 مارس 2014 //drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287 // يعمل إريك برونو في القطاع المالي والمدونات للموقع د. دوب.
يعمل الإصدار الجديد من Java على تسهيل التفاعل مع المصفوفات بالتوازي - مما يؤدي إلى تحسين الأداء بشكل ملحوظ مع الحد الأدنى من الترميز. الآن، تقوم Oracle بإصدار Java SE 8 - وهي خطوة كبيرة إلى الأمام من حيث اللغة. أحد الميزات المهمة لهذا الإصدار هو تحسين التزامن، والذي يظهر بعض منه في الفئة الأساسية java.util.Arrays. تمت إضافة طرق جديدة إلى هذا الفصل، والتي سأصفها في هذه المقالة. يتم استخدام بعض هذه العناصر في ميزة جديدة أخرى لـ JDK8 - lambdas. ولكن دعونا ننكب على العمل.
المصفوفات.paralellSort()
تعتمد العديد من ميزات ParallelSort على خوارزمية فرز دمج متوازية تقوم بتقسيم المصفوفة إلى أجزاء بشكل متكرر، وفرزها، ثم إعادة دمجها في وقت واحد في مصفوفة نهائية. يؤدي استخدامه بدلاً من طريقة Arrays.sort الموجودة والمتسلسلة إلى تحسين الأداء والكفاءة عند فرز المصفوفات الكبيرة. على سبيل المثال، يستخدم الكود أدناه الترتيب المتسلسل () والتوازي المتوازي () لفرز نفس مصفوفة البيانات: public class ParallelSort { public static void main(String[] args) { ParallelSort mySort = new ParallelSort(); int[] src = null; System.out.println("\nSerial sort:"); src = mySort.getData(); mySort.sortIt(src, false); System.out.println("\nParallel sort:"); src = mySort.getData(); mySort.sortIt(src, true); } public void sortIt(int[] src, boolean parallel) { try { System.out.println("--Array size: " + src.length); long start = System.currentTimeMillis(); if ( parallel == true ) { Arrays.parallelSort(src); } else { Arrays.sort(src); } long end = System.currentTimeMillis(); System.out.println( "--Elapsed sort time: " + (end-start)); } catch ( Exception e ) { e.printStackTrace(); } } private int[] getData() { try { File file = new File("src/parallelsort/myimage.png"); BufferedImage image = ImageIO.read(file); int w = image.getWidth(); int h = image.getHeight(); int[] src = image.getRGB(0, 0, w, h, null, 0, w); int[] data = new int[src.length * 20]; for ( int i = 0; i < 20; i++ ) { System.arraycopy( src, 0, data, i*src.length, src.length); } return data; } catch ( Exception e ) { e.printStackTrace(); } return null; } } للاختبار، قمت بتحميل البيانات الأولية من الصورة إلى المصفوفة، والتي استغرقت 46،083،360 بايت (وسوف تعتمد بياناتك على الصور) الذي سوف تستخدمه). استغرقت طريقة الفرز التسلسلي ما يقرب من 3000 مللي ثانية لفرز المصفوفة على الكمبيوتر المحمول رباعي النواة، بينما استغرقت طريقة الفرز المتوازي حوالي 700 مللي ثانية على الأكثر. أوافق، لا يحدث غالبًا أن يؤدي تحديث اللغة الجديدة إلى تحسين أداء الفصل بمقدار 4 مرات.
Arrays.parallelPrefix()
تطبق الطريقة المتوازية دالة رياضية محددة على عناصر المصفوفة بشكل جماعي، وتعالج النتائج داخل المصفوفة بالتوازي. يعد هذا أكثر كفاءة على الأجهزة الحديثة متعددة النواة، مقارنة بالعمليات المتسلسلة على المصفوفات الكبيرة. هناك العديد من تطبيقات هذه الطريقة لأنواع أساسية مختلفة من عمليات البيانات (على سبيل المثال، IntBinaryOperator، وDoubleBinaryOperator، وLongBinaryOperator، وما إلى ذلك)، بالإضافة إلى أنواع مختلفة من العوامل الرياضية. فيما يلي مثال على تكديس المصفوفة المتوازية باستخدام نفس المصفوفة الكبيرة مثل المثال السابق، والتي اكتملت في حوالي 100 مللي ثانية على الكمبيوتر المحمول رباعي النواة. public class MyIntOperator implements IntBinaryOperator { @Override public int applyAsInt(int left, int right) { return left+right; } } public void accumulate() { int[] src = null; // accumulate test System.out.println("\nParallel prefix:"); src = getData(); IntBinaryOperator op = new ParallelSort.MyIntOperator(); long start = System.currentTimeMillis(); Arrays.parallelPrefix(src, new MyIntOperator()); long end = System.currentTimeMillis(); System.out.println("--Elapsed sort time: " + (end-start)); } ... }
Arrays.parallelSetAll()
تقوم الطريقة الموازية SetAll () الجديدة بإنشاء مصفوفة وتعيين كل عنصر من عناصر المصفوفة إلى قيمة وفقًا للدالة التي أنشأت تلك القيم، وذلك باستخدام التوازي لتحسين الكفاءة. تعتمد هذه الطريقة على لامدا (تسمى "الإغلاقات" في اللغات الأخرى) (ونعم، هذا خطأ المؤلف، لأن لامدا والإغلاقات شيئان مختلفان) وهي ميزة جديدة أخرى لـ JDK8 والتي سنناقشها في المقالات المستقبلية. يكفي أن نلاحظ أن lambdas، التي يسهل على عامل التشغيل -> التعرف على تركيبها، تقوم بإجراء عملية على الجانب الأيمن بعد تمرير السهم لجميع العناصر إليها. في مثال التعليمات البرمجية أدناه، يتم تنفيذ الإجراء لكل عنصر في المصفوفة، مفهرسة بواسطة i. يقوم Array.parallelSetAll() بإنشاء عناصر المصفوفة. على سبيل المثال، التعليمة البرمجية التالية تملأ مصفوفة كبيرة بقيم أعداد صحيحة عشوائية: public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( new Random().nextInt())); } لإنشاء منشئ عناصر مصفوفة أكثر تعقيدًا (على سبيل المثال، مولد من شأنه إنشاء قيم بناءً على قراءات من أجهزة استشعار في العالم الحقيقي)، يمكنك استخدام تعليمات برمجية مشابهة لـ ما يلي: public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( customGenerator(getNextSensorValue()))); } public int customGenerator(int arg){ return arg + 1; // some fancy formula here... } public int getNextSensorValue() { // Just random for illustration return new Random().nextInt(); } سنبدأ بـ getNextSensorValue، والذي سيطلب في الواقع من المستشعر (على سبيل المثال، مقياس الحرارة) إرجاع قيمته الحالية. هنا، على سبيل المثال، يتم إنشاء قيمة عشوائية. تقوم طريقة customGenerator() التالية بإنشاء مصفوفة من العناصر باستخدام المنطق المحدد بناءً على الحالة التي تحددها. إليك إضافة صغيرة، لكن في الحالات الحقيقية، سيكون الأمر أكثر تعقيدًا.
ما هو سبلتريتور؟
إضافة أخرى إلى فئة Arrays التي تستخدم التزامن و lambdas هي Spliterator، والذي يستخدم لتكرار المصفوفة وتقسيمها. لا يقتصر تأثيره على المصفوفات - فهو يعمل أيضًا بشكل جيد مع فئات التجميع وقنوات الإدخال والإخراج. تعمل أدوات التقسيم عن طريق تقسيم المصفوفة تلقائيًا إلى أجزاء مختلفة، ويتم تثبيت أداة تقسيم جديدة لإجراء العمليات على هذه المصفوفات الفرعية المرتبطة. يتكون اسمها من Iterator، الذي "يقسم" عمله المتمثل في نقل التكرار إلى أجزاء. باستخدام نفس البيانات لدينا، يمكننا تنفيذ إجراء مقسم على مصفوفتنا على النحو التالي: تنفيذ الإجراءات على البيانات بهذه الطريقة يستفيد من التوازي. يمكنك أيضًا تعيين معلمات التقسيم، مثل الحد الأدنى لحجم كل مصفوفة فرعية. public void spliterate() { System.out.println("\nSpliterate:"); int[] src = getData(); Spliterator spliterator = Arrays.spliterator(src); spliterator.forEachRemaining( n -> action(n) ); } public void action(int value) { System.out.println("value:"+value); // Perform some real work on this data here... }
تيار - المعالجة
أخيرًا، من المصفوفة، يمكنك إنشاء كائن دفق، والذي يسمح بالمعالجة المتوازية لعينة من البيانات ككل، معممة في تسلسل دفق. الفرق بين مجموعة البيانات والتدفق من JDK8 الجديد هو أن المجموعات تسمح لك بالعمل مع العناصر بشكل فردي عندما لا يفعل ذلك التدفق. على سبيل المثال، مع المجموعات، يمكنك إضافة عناصر، وإزالتها، وإدراجها في المنتصف. لا يسمح لك تسلسل الدفق بمعالجة العناصر الفردية من مجموعة بيانات، ولكنه يسمح لك بدلاً من ذلك بتنفيذ وظائف على البيانات ككل. يمكنك إجراء عمليات مفيدة مثل استخراج قيم محددة فقط (تجاهل التكرارات) من مجموعة، وعمليات تحويل البيانات، وإيجاد الحد الأدنى والحد الأقصى للمصفوفة، ووظائف تقليل الخريطة (المستخدمة في الحوسبة الموزعة)، والعمليات الرياضية الأخرى. يستخدم المثال البسيط التالي التزامن لمعالجة مجموعة من البيانات بالتوازي وجمع العناصر. public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
خاتمة
سيكون Java 8 بالتأكيد أحد أكثر التحديثات المفيدة للغة. الميزات الموازية المذكورة هنا، و lambdas، والعديد من الملحقات الأخرى ستكون موضوعًا لمراجعات Java 8 الأخرى على موقعنا.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION