JavaRush /Blog Java /Random-PL /Operacje równoległe na tablicach w Javie 8 - tłumaczenie
billybonce
Poziom 29
Москва

Operacje równoległe na tablicach w Javie 8 - tłumaczenie

Opublikowano w grupie Random-PL
tłumaczenie artykułu
//Operacje na tablicach równoległych w Javie 8 //Autor: Eric Bruno, 25 marca 2014 r. //drdobbs.com/jvm/parallel-array-operacje-in-Java-8/240166287 //Eric Bruno pracuje w sektorze finansowym i bloguje dla serwisu internetowego dr. Dobba.
Nowa wersja języka Java ułatwia równoległą interakcję z tablicami, co skutkuje znacznie lepszą wydajnością przy minimalnej liczbie kodowania. Teraz Oracle wypuszcza Java SE 8 – co jest ogromnym krokiem naprzód pod względem językowym. Jedną z ważnych funkcji tej wersji jest ulepszona współbieżność, a część z nich pojawia się w klasie bazowej java.util.Arrays. Do tej klasy dodano nowe metody, które opiszę w tym artykule. Niektóre z nich są wykorzystywane w innej nowej funkcji JDK8 – lambdach. Ale przejdźmy do rzeczy.
Tablice.paralellSort()
Wiele funkcji ParaleSort opiera się na algorytmie sortowania równoległego przez scalanie, który rekurencyjnie dzieli tablicę na części, sortuje je, a następnie ponownie łączy w ostateczną tablicę. Użycie jej zamiast istniejącej, sekwencyjnej metody Arrays.sort skutkuje lepszą wydajnością i efektywnością sortowania dużych tablic. Na przykład poniższy kod używa sortowania sekwencyjnego() i równoległego sortowania równoległego() do sortowania tej samej tablicy danych: 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; } } Aby przetestować, załadowałem nieprzetworzone dane z obrazu do tablicy, co zajęło 46 083 360 bajtów (a Twoje będzie zależeć od obrazów z którego będziesz korzystać). Sortowanie tablicy na moim 4-rdzeniowym laptopie metodą sortowania sekwencyjnego trwało prawie 3000 milisekund, podczas gdy metoda sortowania równoległego zajmowała maksymalnie około 700 milisekund. Zgadzam się, nie często zdarza się, że nowa aktualizacja językowa poprawia wydajność zajęć 4-krotnie.
Tablice.parallelPrefix()
Metoda ParalePrefix stosuje określoną funkcję matematyczną do elementów tablicy zbiorczo, przetwarzając wyniki w tablicy równolegle. Jest to znacznie wydajniejsze na nowoczesnym sprzęcie wielordzeniowym w porównaniu z operacjami sekwencyjnymi na dużych tablicach. Istnieje wiele implementacji tej metody dla różnych podstawowych typów operacji na danych (na przykład IntBinaryOperator, DoubleBinaryOperator, LongBinaryOperator itd.), a także dla różnych typów operatorów matematycznych. Oto przykład równoległego układania macierzy przy użyciu tej samej dużej tablicy, co w poprzednim przykładzie, który zakończył się w ciągu około 100 milisekund na moim 4-rdzeniowym laptopie. 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() создает массив и устанавливает каждому элементу массива oznaczający в соответствии с генерирующей эти значения функцией, используя параллельность для повышения эфективности. Этот метод основан на лямбдах(называемых "замыканиями"(closures) в других языках) (и, да, тут ошибка автора, ибо лямбды и замыкания это разные вещи) , и которые являются еще одной новинкой JDK8, которую мы обсудим в будущих статьях. Будет достаточно заметить, лямбды, чей синтаксис легко опознать по оператору ->, производящему операцию над правой частью после стрелки для всех переданных ему элементов. В примере kodа, представленном ниже - действие производится для каждого element в массиве, проиндексированного по i. Array.parallelSetAll() генерирует элементы массива. Например, следующий kod заполняет большой массив случайными integer-значениями: public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( new Random().nextInt())); } Для создания более сложного генератора элементов массива(например, такого что генерировал бы значения на основе считывания с датчиков из реального мира), можно использовать kod близкий к следующему: 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, который в реальности будет запрашивать датчик(например термометр) вернуть ему текущее oznaczający. Здесь же для примера генерируется случайное oznaczający. Следующий customGenerator() метод генерирует массив элементов с использованием выбранной логики на основе выбранного вами случая. Вот небольшое дополнение, но для реальных случаев, тут было бы что-нибудь посложнее.
Co такое 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... } Выполнение действий над данными таким образом использует плюсы параллельности. Вы можете также задать параметры сплититератора, такие Jak минимальный размер каждого подмассива.
Stream - обработка
Na koniec z obiektu Array można utworzyć obiekt Stream, który umożliwia równoległe przetwarzanie całej próbki danych uogólnionej w sekwencję strumienia. Różnica między kolekcją danych a strumieniem z nowego JDK8 polega na tym, że kolekcje umożliwiają indywidualną pracę z elementami, gdy strumień tego nie umożliwia. Na przykład w przypadku kolekcji możesz dodawać elementy, usuwać je i wstawiać pośrodku. Sekwencja strumienia nie pozwala na manipulowanie pojedynczymi elementami zbioru danych, lecz umożliwia wykonywanie funkcji na danych jako całości. Możesz wykonywać takie przydatne operacje, jak wyodrębnianie tylko określonych wartości (ignorując powtórzenia) ze zbioru, operacje transformacji danych, znajdowanie minimum i maksimum tablicy, funkcje map-reduce (stosowane w obliczeniach rozproszonych) i inne operacje matematyczne. Poniższy prosty przykład wykorzystuje współbieżność do równoległego przetwarzania tablicy danych i sumowania elementów. public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
Wniosek
Java 8 z pewnością będzie jedną z najbardziej przydatnych aktualizacji języka. Wspomniane tutaj funkcje równoległe, lambdy i wiele innych rozszerzeń będą przedmiotem innych recenzji Java 8 w naszej witrynie.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION