これは、今日の別の質問に対する回答の「副作用」として現れました。それは実際の問題よりも好奇心に関するものです。
Java SE 7は、Oracleが「フォーク/結合フレームワーク」と呼ぶものを提供します。これは、複数のプロセッサに作業をスケジュールするためのおそらく優れた方法です。それがどのように機能するかは理解していますが、それがどこで優れているのか、そして仕事を盗むことについての主張は理解できません。
たぶん、他の誰かが、なぜこのアプローチが望ましいのかについてより多くの洞察を持っているでしょう(それが派手な名前を持っているという理由以外で)。
fork / joinの基礎となるプリミティブはForkJoinTask
sであり、これはFuture
sであり、作業をすぐに実行するという考え方です[原文のまま](「すぐに」という表現は、メインスレッドで同期的に発生することを意味するため、誤解を招く可能性があります。実際には、これは内部で発生します。 a Future
)特定のしきい値を下回るか、しきい値に達するまで作業を2つのタスクに再帰的に分割します。
未来とは、不透明で不特定の方法で非同期的に実行されるタスクをオブジェクトにカプセル化するという概念です。結果が利用可能かどうかを確認できる関数があり、結果を(待機して)取得できる関数があります。
厳密に言えば、futureが非同期で実行されるかどうかさえわかりません。それは、内部で実行される可能性get()
があります。理論的には、実装は将来ごとにスレッドを生成したり、スレッドプールを使用したりすることもできます。
実際には、Javaは、スレッドプールが接続されたタスクキューにタスクとしてfuturesを実装します(同じことがフォーク/ジョインフレームワーク全体にも当てはまります)。
フォーク/結合のドキュメントには、この具体的な使用例が示されています。
protected void compute() {
if (mLength < sThreshold) {
computeDirectly();
return;
}
int split = mLength / 2;
invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
new ForkBlur(mSource, mStart + split, mLength - split,
mDestination));
}
これにより、Mergesortがタスクをトラバースする方法と同じ方法で、基になるスレッドプールのタスクキューにタスクが送信されます(再帰のおかげで)。
たとえば、処理する32個の「アイテム」の配列があり、しきい値が4で、均等に分割すると、それぞれ4個の「アイテム」を持つ8つのタスクが生成され、次のようになります。
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
.
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15|16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
. . .
00 01 02 03 04 05 06 07|08 09 10 11 12 13 14 15|16 17 18 19 20 21 22 23|24 25 26 27 28 29 30 31
. . . . . . .
00 01 02 03|04 05 06 07|08 09 10 11|12 13 14 15|16 17 18 19|20 21 22 23|24 25 26 27|28 29 30 31
------------------------------------------------------------------------------------------------
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
シングルコアプロセッサでは、これによりタスクグループ1-2-3-4-5-6-7-8が順番に送信/実行されます(非常に複雑な方法で)。
デュアルコアプロセッサでは、これは(1,3)-(2,4)-(5,7)-(6,8) [1]を送信/実行します。
クアッドコアプロセッサでは、これは(1,3,5,7)-(2,4,6,8)を送信/実行します。
それに比べて、優れた魔法がすべてない単純な実装では、タスク1-2-3-4-5-6-7-8をすぐにタスクキューに送信するだけです。いつも。
シングルコアプロセッサでは、これは1-2-3-4-5-6-7-8を送信/実行します。
デュアルコアプロセッサでは、これは(1,2)-(3,4)-(5,6)-(7,8)を送信/実行します。
クアッドコアプロセッサでは、これは(1,2,3,4)-(5,6,7,8)を送信/実行します。
質問:
sThresholdの連続するアイテムを1つのタスクに詰め込み、スレッドプールのタスクキューに次々にタスクを送信する代わりに、ツリーのような再帰階層が生成されます。これには、実際には何も行わないN個のサブタスクのN + log2(N)オブジェクトの構築、参照、および破棄が含まれます。なぜこれが優れているのですか?
参照の局所性は保持されません。プロセッサキャッシュも仮想メモリも、そのように扱われるようなものではありません。なぜこれが優れているのですか?
ユニプロセッサシステムを除いて、タスクは元の順序に近い順序でスケジュールされないことが保証されています。それが本当に問題でなければ、これは問題ではないかもしれませんが、それは例えば柵や障壁のようなものをかなり実行不可能にします。フェンスのようなものを作成する唯一の方法は、ルートオブジェクトが完了するのを待ってから、新しいタスクを送信することだけです。これは、完全なパイプラインストールに相当します(これはまさにあなたが決して起こりたくないことです)。
Oracleのドキュメントによると、このアプローチは作業の盗用を実装しているため、スレッドプールよりも優れています。私はこれが起こっているのを見ていません。私が見ることができるのは、単純な通常のスレッドプールにタスクを送信する非常に複雑な方法です。これはどのように魔法のように仕事を盗むことを実装することになっていますか?
[1]複雑にしすぎないようにし、ワーカースレッドが互いに追い越さないことを前提としましょう。タスクはすべて、処理に同じ時間がかかります。そうしないと、送信は同じですが、実行はもちろん異なる順序で発生する可能性があります。