近年のマルチコアCPUの出現により、並列プログラミングは新しい処理作業の馬を最大限に活用する方法です。 並列プログラミング 複数の処理コアが利用可能であるため、プロセスを同時に実行することを指します。これは、本質的に、線形シングルコア実行やマルチスレッドとは対照的に、プログラムのパフォーマンスと効率を大幅に向上させることにつながります。 Fork / Joinフレームワークは、Java同時実行APIの一部です。このフレームワークにより、プログラマーはアルゴリズムを並列化できます。この記事では、Javaで利用可能なFork /JoinFrameworkを使用した並列プログラミングの概念について説明します。
概要
並列プログラミングにははるかに広い意味があり、間違いなく数行で詳しく説明するための広大な領域です。問題の核心は非常に単純ですが、操作上達成するのははるかに困難です。簡単に言うと、並列プログラミングとは、複数のプロセッサを使用してタスクを完了するプログラムを作成することを意味します。それだけです。何だと思う;おなじみですね。それはほとんどマルチスレッドのアイデアで韻を踏んでいます。ただし、それらの間にはいくつかの重要な違いがあることに注意してください。表面的には同じですが、底流はまったく異なります。実際、マルチスレッドは、実際の並列実行がまったくない並列処理の一種の錯覚を提供するために導入されました。マルチスレッドが実際に行うことは、CPUのアイドル時間を盗み、それを有利に使用することです。
要するに、マルチスレッドは、別のスレッドが一時的にユーザー入力などを待機している間に、CPU時間のシェアを取得するために実行されるタスクの個別の論理ユニットのコレクションです。アイドル状態のCPU時間は、競合するスレッド間で最適に共有されます。 CPUが1つしかない場合は、時間共有されます。複数のCPUコアがある場合、それらもすべて時間共有されます。したがって、最適なマルチスレッドプログラムは、タイムシェアリングの巧妙なメカニズムによってCPUのパフォーマンスを絞り出します。本質的には、別のスレッドが待機している間、常に1つのCPUを使用する1つのスレッドです。これは微妙な方法で発生し、ユーザーは並列処理の感覚を得ることができます。実際には、処理は実際にはすばやく連続して行われています。マルチスレッドの最大の利点は、処理リソースを最大限に活用する手法であるということです。現在、このアイデアは非常に有用であり、単一のCPUまたは複数のCPUを備えているかどうかに関係なく、あらゆる環境セットで使用できます。考え方は同じです。
一方、並列プログラミングとは、プログラマーが並列に利用する専用のCPUが複数あることを意味します。このタイプのプログラミングは、マルチコアCPU環境向けに最適化されています。今日のマシンのほとんどはマルチコアCPUを使用しています。したがって、並列プログラミングは最近非常に重要です。最も安価なマシンでさえ、マルチコアCPUが搭載されています。ハンドヘルドデバイスを見てください。それらもマルチコアです。マルチコアCPUではすべてが厄介なように見えますが、ここにも話の別の側面があります。より多くのCPUコアは、より高速または効率的なコンピューティングを意味しますか?常にではない! 「もっと楽しい」という貪欲な哲学は、コンピューティングにも人生にも当てはまりません。しかし、それらは無視できないほどそこにあります—デュアル、クワッド、オクタなど。少なくともほとんどの場合、必要なためではなく、必要なために存在します。実際には、日常のコンピューティングで1つのCPUをビジー状態に保つことは比較的困難です。ただし、マルチコアは、サーバーやゲームなどの特別な状況で、または大きな問題を解決する場合に使用されます。複数のCPUを使用する場合の問題は、処理能力と速度を一致させる必要のあるメモリと、超高速のデータチャネルやその他のアクセサリが必要になることです。つまり、日常のコンピューティングにおける複数のCPUコアは、それを使用するために必要なリソースの量を上回ることのできないパフォーマンスの向上を提供します。その結果、十分に活用されていない高価なマシンを手に入れました。おそらく、展示することだけを目的としています。
並列プログラミング
各タスクがより大きなタスクの個別の論理ユニットであるマルチスレッドとは異なり、並列プログラミングタスクは独立しており、実行順序は重要ではありません。タスクは、実行する機能または処理で使用されるデータに従って定義されます。これは機能的並列処理と呼ばれます またはデータ並列処理 、 それぞれ。機能的並列処理では、各プロセッサは問題のそのセクションで動作しますが、データ並列処理では、プロセッサはデータのそのセクションで動作します。並列プログラミングは、単一のCPUアーキテクチャに適合しない、より大きな問題ベースに適しています。または、問題が非常に大きいため、妥当な時間の見積もりでは解決できない場合があります。その結果、タスクをプロセッサ間で分散させると、比較的高速に結果を得ることができます。
フォーク/結合フレームワーク
Fork / Join Frameworkは、 java.util.concurrentで定義されています。 パッケージ。並列プログラミングをサポートするいくつかのクラスとインターフェースが含まれています。主に行うことは、複数のスレッドの作成とその使用のプロセスを簡素化し、複数のプロセッサ間のプロセス割り当てのメカニズムを自動化することです。このフレームワークを使用したマルチスレッドと並列プログラミングの顕著な違いは、前述したものと非常によく似ています。ここでは、単一CPUのアイドル時間が共有時間に基づいて最適化されるマルチスレッドとは異なり、処理部分は複数のプロセッサを使用するように最適化されています。このフレームワークの追加の利点は、並列実行環境でマルチスレッドを使用することです。そこに害はありません。
このフレームワークには4つのコアクラスがあります:
- ForkJoinTask
: これは、タスクを定義する抽象クラスです。通常、タスクは fork()を使用して作成されます。 このクラスで定義されたメソッド。このタスクは、スレッドで作成された通常のスレッドとほぼ同じです。 クラスですが、それよりも軽いです。適用されるメカニズムは、 ForkJoinPool に参加する少数の実際のスレッドの助けを借りて、多数のタスクの管理を可能にすることです。 。 fork() メソッドは、呼び出しタスクの非同期実行を有効にします。 join() メソッドを使用すると、呼び出されたタスクが最終的に終了するまで待機できます。 invoke()と呼ばれる別のメソッドがあります 、フォークを組み合わせたもの および参加 単一の呼び出しへの操作。 - ForkJoinPool: このクラスは、 ForkJoinTaskの実行を管理するための共通プールを提供します タスク。基本的に、 ForkJoinTask以外からの送信のエントリポイントを提供します。 クライアント、および管理と監視の操作。
- RecursiveAction: これは、 ForkJoinTaskの抽象的な拡張機能でもあります。 クラス。通常、このクラスを拡張して、結果を返さないタスクや voidを持つタスクを作成します。 リターンタイプ。 compute() このクラスで定義されたメソッドは、タスクの計算コードを含めるためにオーバーライドされます。
- RecursiveTask
: これは、 ForkJoinTaskのもう1つの抽象的な拡張機能です。 クラス。このクラスを拡張して、結果を返すタスクを作成します。また、ResursiveActionと同様に、保護されたabstractcompute()も含まれています。 方法。このメソッドは、タスクの計算部分を含めるためにオーバーライドされます。
フォーク/結合フレームワーク戦略
このフレームワークは、再帰的な分割統治を採用しています。 並列処理を実装するための戦略。基本的に、タスクをより小さなサブタスクに分割します。次に、各サブタスクはさらにサブサブタスクに分割されます。このプロセスは、順次処理できるほど小さくなるまで、各タスクに再帰的に適用されます。 Nの配列の値をインクリメントするとします。 数字。これが課題です。これで、配列を2つに分割して、2つのサブタスクを作成できます。それらのそれぞれをさらに2つのサブタスクに分割します。このようにして、分割統治を適用できます。 タスクがユニットの問題に特定されるまで、戦略を再帰的に実行します。このユニットの問題は、使用可能なマルチコアプロセッサによって並行して実行できます。非並列環境では、配列全体を循環して処理を順番に実行する必要がありました。これは、並列処理の観点からは明らかに非効率的なアプローチです。しかし、本当の問題は、すべての問題を分割統治できるかどうかです。 ?絶対にNO!しかし、このアプローチに特に適したデータのグループ化のある種の配列、収集を伴うことが多い問題があります。ちなみに、データの収集を使用しないかもしれないが、並列プログラミングの戦略を使用するように最適化できる問題があります。並列処理や並列アルゴリズムの説明に適した計算問題の種類は、この記事の範囲外です。 Fork /JoinFrameworkのアプリケーションの簡単な例を見てみましょう。
簡単な例
これは、Fork /JoinFrameworkを使用してJavaで並列処理を実装する方法を理解するための非常に簡単な例です。
package org.mano.example; import java.util.concurrent.RecursiveAction; public class CustomRecursiveAction extends RecursiveAction { final int THRESHOLD = 2; double [] numbers; int indexStart, indexLast; CustomRecursiveAction(double [] n, int s, int l) { numbers = n; indexStart = s; indexLast = l; } @Override protected void compute() { if ((indexLast - indexStart) > THRESHOLD) for (int i = indexStart; i < indexLast; i++) numbers [i] = numbers [i] + Math.random(); else invokeAll (new CustomRecursiveAction(numbers, indexStart, (indexStart - indexLast) / 2), new CustomRecursiveAction(numbers, (indexStart - indexLast) / 2, indexLast)); } } package org.mano.example; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { final int SIZE = 10; ForkJoinPool pool = new ForkJoinPool(); double na[] = new double [SIZE]; System.out.println("initialized random values :"); for (int i = 0; i < na.length; i++) { na[i] = (double) i + Math.random(); System.out.format("%.4f ", na[i]); } System.out.println(); CustomRecursiveAction task = new CustomRecursiveAction(na, 0, na.length); pool.invoke(task); System.out.println("Changed values :"); for (inti = 0; i < 10; i++) System.out.format("%.4f ", na[i]); System.out.println(); } }
結論
これは、並列プログラミングとそれがJavaでどのようにサポートされているかについての簡潔な説明です。 N を持っていることは、確立された事実です。 コアがすべてをNにするわけではありません 倍速くなります。 Javaアプリケーションの一部のみがこの機能を効果的に使用します。並列プログラミングコードは難しいフレームです。さらに、効果的な並列プログラムでは、負荷分散、並列タスク間の通信などの問題を考慮する必要があります。並列実行により適したアルゴリズムがいくつかありますが、多くはそうではありません。いずれにせよ、JavaAPIはそのサポートに不足していません。 APIをいじくり回して、何が最適かを見つけることができます。ハッピーコーディング🙂