JavaRush /Java Blog /Random-JA /Java 8 での配列の並列操作 - 翻訳
billybonce
レベル 29
Москва

Java 8 での配列の並列操作 - 翻訳

Random-JA グループに公開済み
記事の翻訳
//Java 8 での並列配列操作 //Eric Bruno 著、2014 年 3 月 25 日 //drdobbs.com/jvm/Parallel-array-operations-in-java-8/240166287 //Eric Bruno は金融部門とブログで働いていますウェブサイトのDr. ドブさん。
Java の新しいリリースでは、配列の並列操作が容易になり、最小限のコーディングでパフォーマンスが大幅に向上します。現在、Oracle は Java SE 8 をリリースしています。これは言語の点で大きな前進です。このリリースの重要な機能の 1 つは同時実行性の向上であり、その一部は java.util.Arrays 基本クラスに含まれています。このクラスに新しいメソッドが追加されました。これについては、この記事で説明します。これらの一部は、JDK8 の別の新機能であるラムダで使用されます。しかし、本題に入りましょう。
Arrays.paralellSort()
ParallelSort の機能の多くは、配列を再帰的に部分に分割し、並べ替えて、最終的な配列に同時に再結合する並列マージ ソート アルゴリズムに基づいています。既存の順次 Arrays.sort メソッドの代わりにこれを使用すると、大きな配列を並べ替える際のパフォーマンスと効率が向上します。 たとえば、以下のコードは、同じデータ配列を並べ替えるために逐次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バイトかかりました(実際のデータは画像によって異なります)。これを使用します)。私の 4 コア ラップトップで配列をソートするのに、順次ソート方式では約 3,000 ミリ秒かかりましたが、並列ソート方式では最大で約 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() メソッドは配列を作成し、並列処理を使用して効率を向上させ、それらの値を生成した関数に従って各配列要素を値に設定します。このメソッドはラムダ (他の言語では「クロージャ」と呼ばれます) に基づいています (ラムダとクロージャは別のものなので、これは作成者の間違いです) これは JDK8 のもう 1 つの新機能であり、今後の記事で説明します。ラムダの構文は -> 演算子で簡単に認識でき、渡されたすべての要素に対して矢印の後の右側で演算を実行することに注意するだけで十分です。以下のコード例では、i でインデックス付けされた配列内の各要素に対してアクションが実行されます。Array.ParallelSetAll() は配列要素を生成します。たとえば、次のコードは、大きな配列にランダムな整数値を入力します。 public void createLargeArray() { Integer[] array = new Integer[1024*1024*4]; // 4M Arrays.parallelSetAll( array, i -> new Integer( new Random().nextInt())); } より複雑な配列要素ジェネレーター (たとえば、実世界のセンサーからの読み取り値に基づいて値を生成するもの) を作成するには、次のようなコードを使用できます。 getNextSensorValue から始めます。 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(); } 実際には、センサー (温度計など) に現在の値を返すように要求します。ここでは例としてランダムな値を生成します。次のcustomGenerator()メソッドは、選択したケースに基づいて、選択したロジックを使用して要素の配列を生成します。ここに少し追加しますが、実際のケースでは、さらに複雑になります。
スプリテレーターとは何ですか?
同時実行性とラムダを利用する Arrays クラスへのもう 1 つの追加機能は、配列の反復と分割に使用される Spliterator です。その効果は配列に限定されるものではなく、コレクション クラスや IO チャネルにも有効です。スプリッテレータは、配列をさまざまな部分に自動的に分割することによって機能し、これらのリンクされたサブ配列に対して操作を実行するために新しいスプリッテレータがインストールされます。その名前は、反復の移動作業を部分に「分割」する Iterator で構成されています。同じデータを使用して、次のように配列に対して分割反復アクションを実行できます。 この方法でデータに対してアクションを実行すると、並列処理が利用されます。各サブ配列の最小サイズなどのスプリッター パラメーターを設定することもできます。 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... }
ストリーム - 処理
最後に、配列から Stream オブジェクトを作成できます。これにより、ストリーム シーケンスに一般化されたデータのサンプル全体を並列処理できます。データのコレクションと新しい JDK8 のストリームの違いは、ストリームでは個別に要素を操作できない場合でも、コレクションでは要素を個別に操作できることです。たとえば、コレクションの場合、要素を追加したり、要素を削除したり、中央に挿入したりできます。Stream シーケンスでは、データ セットの個々の要素を操作することはできませんが、代わりにデータ全体に対して関数を実行できます。セットからの特定の値のみの抽出 (繰り返しを無視)、データ変換操作、配列の最小値と最大値の検索、マップリデュース関数 (分散コンピューティングで使用)、その他の数学的演算などの便利な操作を実行できます。次の簡単な例では、同時実行性を使用してデータの配列を並列処理し、要素を合計します。 public void streamProcessing() { int[] src = getData(); IntStream stream = Arrays.stream(src); int sum = stream.sum(); System.out.println("\nSum: " + sum); }
結論
Java 8 は間違いなく、この言語の最も有用なアップデートの 1 つとなるでしょう。ここで説明した並列機能、ラムダ、その他の多くの拡張機能は、当サイトの他の Java 8 レビューの対象となるでしょう。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION