JavaRush /Blogue Java /Random-PT /Operações paralelas em arrays em Java 8 - tradução
billybonce
Nível 29
Москва

Operações paralelas em arrays em Java 8 - tradução

Publicado no grupo Random-PT
tradução do artigo
//Operações de array paralelo em Java 8 //Por Eric Bruno, 25 de março de 2014 //drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287 //Eric Bruno trabalha no setor financeiro e tem blogs para o site Dr. Dobb.
A nova versão do Java facilita a interação com arrays em paralelo – resultando em um desempenho significativamente melhorado com um mínimo de codificação. Agora, a Oracle está lançando o Java SE 8 – o que é um grande avanço em termos de linguagem. Um dos recursos importantes desta versão é a simultaneidade aprimorada, algumas das quais aparecem na classe base java.util.Arrays. Novos métodos foram adicionados a esta classe, que descreverei neste artigo. Alguns deles são usados ​​em outro novo recurso do JDK8 – lambdas. Mas vamos ao que interessa.
Arrays.paralellSort()
Muitos dos recursos do paraleloSort são baseados em um algoritmo de classificação por mesclagem paralela que divide recursivamente um array em partes, classifica-as e depois as recombina simultaneamente em um array final. Usá-lo em vez do método Arrays.sort sequencial existente resulta em melhor desempenho e eficiência ao classificar grandes matrizes. Por exemplo, o código abaixo usa ordenação sequencial() e paralela paralelaSort() para classificar o mesmo array de dados: 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; } } Para testar, carreguei os dados brutos da imagem no array, que ocupou 46.083.360 bytes (e o seu dependerá das imagens que você usará). O método de classificação sequencial levou quase 3.000 milissegundos para classificar o array em meu laptop de 4 núcleos, enquanto o método de classificação paralela levou no máximo cerca de 700 milissegundos. Concordo, não é sempre que uma nova atualização de idioma melhora o desempenho da aula em 4 vezes.
Arrays.parallelPrefix()
O método paraleloPrefix aplica uma função matemática especificada aos elementos de uma matriz coletivamente, processando os resultados dentro da matriz em paralelo. Isso é muito mais eficiente em hardware moderno com vários núcleos, em comparação com operações sequenciais em grandes arrays. Existem muitas implementações deste método para diferentes tipos básicos de operações de dados (por exemplo, IntBinaryOperator, DoubleBinaryOperator, LongBinaryOperator e assim por diante), bem como para diferentes tipos de operadores matemáticos. Aqui está um exemplo de empilhamento de array paralelo usando o mesmo array grande do exemplo anterior, que foi concluído em cerca de 100 milissegundos em meu laptop de 4 núcleos. 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 - обработка
Finalmente, a partir de um Array, você pode criar um objeto Stream, que permite o processamento paralelo de uma amostra de dados como um todo, generalizada em uma sequência de fluxo. A diferença entre uma Coleção de dados e um Stream do novo JDK8 é que as coleções permitem trabalhar com elementos individualmente, enquanto um Stream não o faz. Por exemplo, com coleções você pode adicionar elementos, removê-los e inseri-los no meio. Uma sequência Stream não permite manipular elementos individuais de um conjunto de dados, mas permite executar funções nos dados como um todo. Você pode realizar operações úteis como extrair apenas valores específicos (ignorando repetições) de um conjunto, operações de transformação de dados, encontrar o mínimo e o máximo de uma matriz, funções de redução de mapa (usadas em computação distribuída) e outras operações matemáticas. O exemplo simples a seguir usa simultaneidade para processar uma matriz de dados em paralelo e somar os elementos. public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
Conclusão
Java 8 será definitivamente uma das atualizações mais úteis para a linguagem. Os recursos paralelos mencionados aqui, lambdas e muitas outras extensões serão objeto de outras análises do Java 8 em nosso site.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION