JavaRush /Java блогу /Random-KY /Java 8де массивдер боюнча параллелдүү операциялар - котор...
billybonce
Деңгээл
Москва

Java 8де массивдер боюнча параллелдүү операциялар - которуу

Группада жарыяланган
макаланын котормосу
//Java 8де параллелдүү массив операциялары //Эрик Бруно тарабынан, 2014-жылдын 25-марты //drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287 //Эрик Бруно каржы секторунда жана блогдордо иштейт веб-сайт үчүн Dr. Доббдун.
Java-нын жаңы чыгарылышы массивдер менен параллелдүү өз ара аракеттенүүнү жеңилдетет - натыйжада минималдуу codeдоо менен иштөө кыйла жакшырды. Азыр Oracle Java SE 8ди чыгарат - бул тил жагынан алдыга чоң кадам. Бул чыгарылыштын маанилүү өзгөчөлүктөрүнүн бири - жакшыртылган параллелдүүлүк, алардын айрымдары java.util.Arrays базалык классында пайда болот. Бул класска жаңы ыкмалар кошулду, аларды мен бул макалада сүрөттөп берем. Алардын айрымдары JDK8дин дагы бир жаңы өзгөчөлүгү - ламбдаларда колдонулат. Бирок, келгиле, бизнеске киришели.
Arrays.paralellSort()
ParallelSort'тун көптөгөн функциялары массивди рекурсивдүү түрдө бөлүктөргө бөлүүчү, аларды сорттоочу жана бир эле учурда акыркы массивге кайра бириктирүүчү параллелдүү бириктирүү сорттоо алгоритмине негизделген. Аны учурдагы, ырааттуу Arrays.sort ыкмасынын ордуна колдонуу чоң массивдерди сорттоодо иштөөнүн жана эффективдүүлүктүн жакшырышына алып келет. Мисалы, төмөндөгү code бир эле маалымат массивин сорттоо үчүн ырааттуу sort() жана параллелдүү parallelSort() колдонот: 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 byteты алган (жана сиздики сүрөттөргө жараша болот) сиз колдоносуз). Менин 4 ядролуу ноутбукумда массивди иреттөө үчүн ырааттуу сорттоо ыкмасы дээрлик 3000 миллисекундду талап кылды, ал эми параллелдүү сорттоо ыкмасы эң көп дегенде 700 миллисекундду талап кылды. Макул, жаңы тил жаңыртуу класстын көрсөткүчүн 4 эсеге жакшыртат деп көп кездешпейт.
Arrays.parallelPrefix()
ParallelPrefix методу белгиленген математикалык функцияны массивдин элементтерине биргелешип колдонот, натыйжаларды параллелдүү түрдө массивдин ичинде иштетет. Бул чоң массивдердеги ырааттуу операцияларга салыштырмалуу заманбап көп ядролуу жабдыктарда алда канча натыйжалуу. Маалымат операцияларынын ар кандай негизги түрлөрү үчүн (мисалы, IntBinaryOperator, DoubleBinaryOperator, LongBinaryOperator ж.б.у.с.), ошондой эле математикалык операторлордун ар кандай түрлөрү үчүн бул методдун көптөгөн ишке ашырылышы бар. Бул жерде менин 4 ядролуу ноутбукта 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()
Новый метод parallelSetAll() создает массив и устанавливает каждому элементу массива meaning в соответствии с генерирующей эти значения функцией, используя параллельность для повышения эфективности. Этот метод основан на лямбдах(называемых "замыканиями"(closures) в других языках) (и, да, тут ошибка автора, ибо лямбды и замыкания это разные вещи) , и которые являются еще одной новинкой JDK8, которую мы обсудим в будущих статьях. Будет достаточно заметить, лямбды, чей синтаксис легко опознать по оператору ->, производящему операцию над правой частью после стрелки для всех переданных ему элементов. В примере codeа, представленном ниже - действие производится для каждого element в массиве, проиндексированного по i. Array.parallelSetAll() генерирует элементы массива. Например, следующий code заполняет большой массив случайными integer-значениями: public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( new Random().nextInt())); } Для создания более сложного генератора элементов массива(например, такого что генерировал бы значения на основе считывания с датчиков из реального мира), можно использовать code близкий к следующему: 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, который в реальности будет запрашивать датчик(например термометр) вернуть ему текущее meaning. Здесь же для примера генерируется случайное meaning. Следующий customGenerator() метод генерирует массив элементов с использованием выбранной логики на основе выбранного вами случая. Вот небольшое дополнение, но для реальных случаев, тут было бы что-нибудь посложнее.
What такое Spliterator?
Другое дополнение к классу Arrays, которое использует параллельность и лямбды - это Spliterator, который используется для итерации и разделения массива. Его действие не ограничено только массивами - он также хорошо работает и для классов Collection и IO каналов. Spliterator'ы работают на основе автоматического разбиения массива на различные части, а новый Spliterator устанавливается для того чтобы производить операции над этими связанными подмассивами. Его название составленно из Iterator(итератора), который "разделяет"(splits) его работу по перемещению-итерации на части. Используя наши, всё те же, данные, мы можем произвести раздельноитерированное(splititerated) действие над нашим массивом следующим образом: 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... } Выполнение действий над данными таким образом использует плюсы параллельности. Вы можете также задать параметры сплититератора, такие How минимальный размер каждого подмассива.
Stream - обработка
Акырында, Массивден сиз агымдын ырааттуулугуна жалпыланган, бүтүндөй маалыматтардын үлгүсүн параллелдүү иштетүүгө мүмкүндүк берүүчү Stream an objectин түзө аласыз. Маалыматтар жыйнагы менен жаңы JDK8деги агымдын ортосундагы айырма, Агым иштебегенде, коллекциялар элементтер менен жекече иштөөгө мүмкүндүк берет. Мисалы, коллекциялар менен элементтерди кошуп, аларды алып салып, ортосуна кыстара аласыз. Агым ырааттуулугу маалымат топтомунун айрым элементтерин башкарууга мүмкүндүк бербейт, тескерисинче, бүтүндөй маалыматтар боюнча функцияларды аткарууга мүмкүндүк берет. Сиз топтомдон белгилүү гана маанилерди алуу (кайталанууларды этибарга алуу), маалыматтарды трансформациялоо операциялары, массивдин минималдуу жана максимумдарын табуу, картаны азайтуу функциялары (бөлүштүрүлгөн эсептөөдө колдонулат) жана башка математикалык операциялар сыяктуу пайдалуу операцияларды аткара аласыз. Төмөнкү жөнөкөй мисал параллелдүү маалыматтардын массивдерин иштеп чыгуу жана элементтерди кошуу үчүн параллелдүүлүктү колдонот. public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
Корутунду
Java 8 сөзсүз түрдө тилге эң пайдалуу жаңыртуулардын бири болот. Бул жерде айтылган параллелдүү функциялар, ламбдалар жана башка көптөгөн кеңейтүүлөр биздин сайттагы башка Java 8 сын-пикирлеринин предмети болот.
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION