JavaRush /Blog Java /Random-FR /Opérations parallèles sur les tableaux en Java 8 - traduc...
billybonce
Niveau 29
Москва

Opérations parallèles sur les tableaux en Java 8 - traduction

Publié dans le groupe Random-FR
traduction de l'article
//Opérations Parallel Array en Java 8 //Par Eric Bruno, 25 mars 2014 //drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287 //Eric Bruno travaille dans le secteur financier et blogue pour le site Dr. Dobb's.
La nouvelle version de Java facilite l'interaction avec les tableaux en parallèle, ce qui se traduit par des performances considérablement améliorées avec un minimum de codage. Aujourd'hui, Oracle lance Java SE 8, ce qui constitue un énorme pas en avant en termes de langage. L'une des fonctionnalités importantes de cette version est la concurrence améliorée, dont certaines apparaissent dans la classe de base java.util.Arrays. De nouvelles méthodes ont été ajoutées à cette classe, que je décrirai dans cet article. Certains d'entre eux sont utilisés dans une autre nouvelle fonctionnalité du JDK8 : les lambdas. Mais passons aux choses sérieuses.
Arrays.paralellSort()
De nombreuses fonctionnalités de parallelSort sont basées sur un algorithme de tri par fusion parallèle qui divise de manière récursive un tableau en parties, les trie, puis les recombine simultanément dans un tableau final. Son utilisation à la place de la méthode séquentielle Arrays.sort existante permet d'améliorer les performances et l'efficacité lors du tri de grands tableaux. Par exemple, le code ci-dessous utilise le sort séquentiel() et le parallèle parallèleSort() pour trier le même tableau de données : 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; } } Pour tester, j'ai chargé les données brutes de l'image dans le tableau, ce qui a pris 46 083 360 octets (et les vôtres dépendront des images). que vous utiliserez). La méthode de tri séquentiel a pris près de 3 000 millisecondes pour trier la matrice sur mon ordinateur portable à 4 cœurs, tandis que la méthode de tri parallèle a pris environ 700 millisecondes au maximum. D’accord, il n’arrive pas souvent qu’une nouvelle mise à jour linguistique améliore les performances d’une classe de 4 fois.
Arrays.parallelPrefix()
La méthode parallelPrefix applique collectivement une fonction mathématique spécifiée aux éléments d’un tableau, traitant les résultats dans le tableau en parallèle. Ceci est beaucoup plus efficace sur le matériel multicœur moderne, par rapport aux opérations séquentielles sur de grandes baies. Il existe de nombreuses implémentations de cette méthode pour différents types d'opérations de données de base (par exemple, IntBinaryOperator, DoubleBinaryOperator, LongBinaryOperator, etc.), ainsi que pour différents types d'opérateurs mathématiques. Voici un exemple d'empilement de tableaux parallèles utilisant le même grand tableau que l'exemple précédent, qui s'est terminé en environ 100 millisecondes sur mon ordinateur portable à 4 cœurs. 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 - обработка
Enfin, à partir d'un Array, vous pouvez créer un objet Stream, qui permet un traitement parallèle sur un échantillon de données dans son ensemble, généralisé dans une séquence de flux. La différence entre une collection de données et un flux du nouveau JDK8 est que les collections vous permettent de travailler avec des éléments individuellement alors qu'un flux ne le fait pas. Par exemple, avec les collections, vous pouvez ajouter des éléments, les supprimer et les insérer au milieu. Une séquence Stream ne vous permet pas de manipuler des éléments individuels d'un ensemble de données, mais vous permet plutôt d'exécuter des fonctions sur les données dans leur ensemble. Vous pouvez effectuer des opérations utiles telles que l'extraction uniquement de valeurs spécifiques (en ignorant les répétitions) d'un ensemble, des opérations de transformation de données, la recherche du minimum et du maximum d'un tableau, des fonctions de réduction de carte (utilisées dans l'informatique distribuée) et d'autres opérations mathématiques. L'exemple simple suivant utilise la concurrence pour traiter un tableau de données en parallèle et additionner les éléments. public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
Conclusion
Java 8 sera certainement l'une des mises à jour les plus utiles du langage. Les fonctionnalités parallèles évoquées ici, les lambdas, et bien d'autres extensions feront l'objet d'autres revues Java 8 sur notre site.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION