新しいものを使うメリットは何ですか?フォーク/ジョインフレームワーク最初に大きなタスクをN個のサブタスクに分割し、それらをキャッシュされたスレッドプール(執行者) を実行し、各タスクが完了するのを待つのでしょうか? フォーク/ジョインの抽象化を使用することで、問題がどのように簡素化されるのか、また、これまで何年も使用してきたソリューションよりもソリューションが効率的になるのか、私にはわかりません。
例えば、並列化されたぼかしアルゴリズムは、チュートリアルの例次のように実装できます。
public class Blur implements Runnable {
private int[] mSource;
private int mStart;
private int mLength;
private int[] mDestination;
private int mBlurWidth = 15; // Processing window size, should be odd.
public ForkBlur(int[] src, int start, int length, int[] dst) {
mSource = src;
mStart = start;
mLength = length;
mDestination = dst;
}
public void run() {
computeDirectly();
}
protected void computeDirectly() {
// As in the example, omitted for brevity
}
}
最初に分割してタスクをスレッド プールに送信します。
// source image pixels are in src
// destination image pixels are in dst
// threadPool is a (cached) thread pool
int maxSize = 100000; // analogous to F-J's "sThreshold"
List<Future> futures = new ArrayList<Future>();
// Send stuff to thread pool:
for (int i = 0; i < src.length; i+= maxSize) {
int size = Math.min(maxSize, src.length - i);
ForkBlur task = new ForkBlur(src, i, size, dst);
Future f = threadPool.submit(task);
futures.add(f);
}
// Wait for all sent tasks to complete:
for (Future future : futures) {
future.get();
}
// Done!
タスクはスレッド プールのキューに移動し、ワーカー スレッドが使用可能になるとそこから実行されます。分割が十分に細かく (最後のタスクを特に待つ必要がないように) 行われ、スレッド プールに十分なスレッド (少なくとも N 個のプロセッサ) がある場合、すべてのプロセッサは計算全体が完了するまでフル スピードで動作します。
何か見落としているのでしょうか? フォーク/ジョイン フレームワークを使用することで得られる付加価値は何でしょうか?
ベストアンサー1
基本的な誤解は、Fork/Joinの例がない仕事を見せて窃盗しかし、それはある種の標準的な分割統治にすぎません。
仕事の横取りは、次のようなものです。労働者 B は仕事を終えました。彼は親切な人なので、周りを見回すと、労働者 A がまだ一生懸命働いているのに気づきます。彼は歩いて行って、「やあ、手伝ってあげるよ」と尋ねます。A は答えます。「いいだろう、1000 ユニットの作業があるんだ。今のところ 345 を終えて、655 が残っている。673 から 1000 までやってもらえないか。346 から 672 は私がやるから。」B は「よし、早くパブに行けるように始めよう。」と言います。
ご存知のとおり、作業員は実際の作業を開始した後も、互いに通信する必要があります。これが例に欠けている部分です。
一方、例では「下請け業者を使う」といったことしか示されていません。
作業員 A: 「やれやれ、仕事が 1000 単位ある。私には多すぎる。500 単位は自分でやって、残りの 500 単位は他の人に下請けに出すことにしよう。」 この作業は、大きなタスクが 10 単位ずつの小さなパケットに分割されるまで続きます。これらは、利用可能な作業員によって実行されます。しかし、1 つのパケットが一種のポイズン ピルで、他のパケットよりもかなり時間がかかる場合は、運が悪く、分割フェーズは終了です。
Fork/Join と事前にタスクを分割することの唯一の違いは、事前に分割すると、開始直後から作業キューがいっぱいになることです。例: 1000 ユニット、しきい値は 10 なので、キューには 100 エントリがあります。これらのパケットは、スレッドプール メンバーに配布されます。
Fork/Join はより複雑で、キュー内のパケットの数を少なく保とうとします。
- ステップ1: (1...1000)を含む1つのパケットをキューに入れる
- ステップ 2: 1 人のワーカーがパケット (1...1000) をポップし、2 つのパケット (1...500) と (501...1000) に置き換えます。
- ステップ 3: 1 人のワーカーがパケット (500...1000) をポップし、(500...750) と (751...1000) をプッシュします。
- ステップ n: スタックには次のパケットが含まれています: (1..500)、(500...750)、(750...875)... (991..1000)
- ステップn+1: パケット(991..1000)がポップされ実行される
- ステップn+2: パケット(981..990)がポップされ実行される
- ステップ n+3: パケット (961..980) がポップされ、(961...970) と (971..980) に分割されます。...
ご覧のとおり、Fork/Join ではキューが小さくなり (例では 6)、「分割」フェーズと「作業」フェーズが交互に配置されます。
複数のワーカーが同時にポップおよびプッシュする場合、もちろん相互作用はそれほど明確ではありません。