JavaRush /Java Blog /Random-IT /Operazioni parallele su array in Java 8 - traduzione
billybonce
Livello 29
Москва

Operazioni parallele su array in Java 8 - traduzione

Pubblicato nel gruppo Random-IT
traduzione dell'articolo
//Parallel Array Operations in Java 8 //Di Eric Bruno, 25 marzo 2014 //drdobbs.com/jvm/parallel-array-operazioni-in-java-8/240166287 //Eric Bruno lavora nel settore finanziario e nei blog per il sito web Dott. Dobb's.
La nuova versione di Java semplifica l'interazione con gli array in parallelo, con conseguente miglioramento significativo delle prestazioni con una codifica minima. Ora Oracle sta rilasciando Java SE 8, che rappresenta un enorme passo avanti in termini di linguaggio. Una delle caratteristiche importanti di questa versione è il miglioramento della concorrenza, alcuni dei quali appaiono nella classe base java.util.Arrays. A questa classe sono stati aggiunti nuovi metodi, che descriverò in questo articolo. Alcuni di questi vengono utilizzati in un'altra nuova funzionalità di JDK8: lambda. Ma veniamo al sodo.
Array.paralellSort()
Molte delle funzionalità di parallelSort si basano su un algoritmo di ordinamento parallelo che divide ricorsivamente un array in parti, le ordina e quindi le ricombina simultaneamente in un array finale. Usandolo al posto del metodo sequenziale esistente Arrays.sort si ottengono prestazioni ed efficienza migliorate durante l'ordinamento di array di grandi dimensioni. Ad esempio, il codice seguente utilizza sequenziale sort() e parallelo parallelSort() per ordinare lo stesso array di dati: 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; } } Per testare, ho caricato i dati grezzi dall'immagine nell'array, che ha richiesto 46.083.360 byte (e i tuoi dipenderanno dalle immagini che utilizzerai). Il metodo di ordinamento sequenziale ha impiegato quasi 3.000 millisecondi per ordinare l'array sul mio laptop a 4 core, mentre il metodo di ordinamento parallelo ha impiegato al massimo circa 700 millisecondi. D'accordo, non accade spesso che un nuovo aggiornamento linguistico migliori le prestazioni della classe di 4 volte.
Arrays.parallelPrefix()
Il metodo parallelPrefix applica collettivamente una funzione matematica specificata agli elementi di una matrice, elaborando i risultati all'interno della matrice in parallelo. Ciò è molto più efficiente sui moderni hardware multi-core, rispetto alle operazioni sequenziali su array di grandi dimensioni. Esistono molte implementazioni di questo metodo per diversi tipi di base di operazioni sui dati (ad esempio, IntBinaryOperator, DoubleBinaryOperator, LongBinaryOperator e così via), nonché per diversi tipi di operatori matematici. Ecco un esempio di stacking di array paralleli utilizzando lo stesso array di grandi dimensioni dell'esempio precedente, che è stato completato in circa 100 millisecondi sul mio laptop a 4 core. 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)); } ... }
Array.parallelSetAll()
Il nuovo metodo parallelSetAll() crea un array e imposta ciascun elemento dell'array su un valore in base alla funzione che ha generato tali valori, utilizzando il parallelismo per migliorare l'efficienza. Questo metodo si basa sui lambda (chiamati "chiusure" in altre lingue) (e, sì, questo è un errore dell'autore, perché lambda e chiusure sono cose diverse) , e che sono un'altra novità di JDK8 di cui parleremo nei prossimi articoli. Basterà notare che i lambda, la cui sintassi è facilmente riconoscibile dall'operatore ->, eseguono un'operazione sul lato destro dopo la freccia per tutti gli elementi che gli sono passati. Nell'esempio di codice riportato di seguito, l'azione viene eseguita per ciascun elemento dell'array, indicizzato da i. Array.parallelSetAll() genera elementi dell'array. Ad esempio, il codice seguente riempie un array di grandi dimensioni con valori interi casuali: public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( new Random().nextInt())); } per creare un generatore di elementi di array più complesso (ad esempio, uno che generi valori basati sulle letture dei sensori del mondo reale), è possibile utilizzare un codice simile a quanto segue: 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(); } Inizieremo con getNextSensorValue, che in realtà chiederà al sensore (ad esempio un termometro) di restituire il suo valore corrente. Qui, ad esempio, viene generato un valore casuale. Il seguente metodo customGenerator() genera una serie di elementi utilizzando la logica selezionata in base al caso selezionato. Ecco una piccola aggiunta, ma per i casi reali sarebbe qualcosa di più complicato.
Cos'è lo Spliterator?
Un'altra aggiunta alla classe Arrays che fa uso di concorrenza e lambda è Spliterator, che viene utilizzato per iterare e dividere un array. Il suo effetto non è limitato agli array: funziona bene anche per le classi Collection e i canali IO. Gli Spliterator funzionano suddividendo automaticamente un array in diverse parti e viene installato un nuovo Spliterator per eseguire operazioni su questi sottoarray collegati. Il suo nome è composto da Iterator, che "divide" il suo lavoro di iterazione in movimento in parti. Utilizzando i nostri stessi dati, possiamo eseguire un'azione splititerata sul nostro array come segue: L'esecuzione di azioni sui dati in questo modo sfrutta il parallelismo. È inoltre possibile impostare i parametri del divisore, come la dimensione minima di ciascun sottoarray. 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... }
Flusso: elaborazione
Infine, da un Array è possibile creare un oggetto Stream, che consente l'elaborazione parallela su un campione di dati nel suo insieme, generalizzato in una sequenza di stream. La differenza tra una raccolta di dati e uno stream del nuovo JDK8 è che le raccolte ti consentono di lavorare con gli elementi individualmente quando uno stream no. Ad esempio, con le raccolte puoi aggiungere elementi, rimuoverli e inserirli al centro. Una sequenza Stream non consente di manipolare singoli elementi di un set di dati, ma consente invece di eseguire funzioni sui dati nel loro insieme. È possibile eseguire operazioni utili come estrarre solo valori specifici (ignorando le ripetizioni) da un set, operazioni di trasformazione dei dati, trovare il minimo e il massimo di un array, funzioni di riduzione della mappa (utilizzate nel calcolo distribuito) e altre operazioni matematiche. Il semplice esempio seguente utilizza la concorrenza per elaborare una matrice di dati in parallelo e sommare gli elementi. public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
Conclusione
Java 8 sarà sicuramente uno degli aggiornamenti più utili al linguaggio. Le funzionalità parallele qui menzionate, lambda e molte altre estensioni saranno oggetto di altre recensioni di Java 8 sul nostro sito.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION