ForkJoin を NetBeans 6.8 で試してみた
12月の初旬にリリースされた NetBeans 6.8 を使って JDK 7 に組み入れられる予定の ForkJoin フレームワークを試してみました。
Fork Jion フレームワークの情報は日本語でも少しありましたのでネット上の情報を元に英語の API ドキュメントをちらちら見ながら適当にプログラムを組んでみました。
素人の私がそういう状況で作ったので間違いがあるかもしれないの見つけられた方は優しく解りやすく間違いを正してくれることを望みます。(^^;
まず、JDK 7 build 77 をインストールして NetBeans 6.8 でそれを使えるようにします。(現在は build 78 が最新です)
Java プラットフォームマネージャーにて JDK 7 を登録します。
メニューバーの [ ツール ] から [ Java プラットフォーム ] をクリックして表示される画面の指示に従って入力していきます。
プラットフォーム名は自分の好みでつけてかまいません。
以上で NetBeans 6.8 でターゲット JDK を JDK 7 build77 にすることが簡単にできます。
新規プロジェクトを作成したらデフォルトがターゲット JDK と異なってる場合はプロジェクトのプロパティで Java プラットフォームを変更できます。
先ほど登録した JDK 7 build 77 が利用可能になってますのでそれを選択します。
また、下のほうに「ソース / バイナリ形式」 に JDK 7 が選択可能となりますのでそれを選択します。
以上のようにして下図のようなプロジェクト構成のサンプルプログラムを作ってみました。
分割統治型アルゴリズムで有名なマージソートです。
ちなみに JDK 6 ではこのマージソートアルゴリズムが使われているらしいです。
この JDK 7 build 77 からは java.util.Arraysクラスの定義されている基本データ型の配列のソートは、Dual Pivot Quicksortを呼び出すように書き換えられています。
参照型の配列の場合には、TimSortですので、Java 7ではソートのアルゴリズムが新しくなります。
と ForkJoin フレームワークの情報を探していたときにみつけました。(余計な情報><
RandomNumber2009.RandomNumber.java
package RandomNumber2009;
import java.util.Random;
public class RandomNumber {
private final int[] number = new int[300000];
public RandomNumber() {
Random generator = new Random(1982);
for (int i = 0; i < number.length; i++) {
number[i] = generator.nextInt(300000);
}
}
public int[] getNumber() {
return number;
}
}
forkjointest.MergeSort.java
package forkjointest;
import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
public class MergeSort {
public int[] sort(int[] number) {
int nThreads = Runtime.getRuntime().availableProcessors();
ForkJoinPool pool = new ForkJoinPool(nThreads);
SortImpl sort = new SortImpl(number);
pool.invoke(sort);
return sort.result;
}
private class SortImpl extends RecursiveAction {
private int[] number;
private int[] result;
SortImpl(int[] number) {
this.number = number;
}
@Override
protected void compute() {
if ((number.length < 5)) {
result = Arrays.copyOf(number, number.length);
Arrays.sort(result, 0, result.length);
} else {
int midpoint = number.length / 2;
int[] left = Arrays.copyOfRange(number, 0, midpoint);
int[] right = Arrays.copyOfRange(number, midpoint, number.length);
SortImpl task1 = new SortImpl(left);
SortImpl task2 = new SortImpl(right);
invokeAll(task1, task2);
left = task1.result;
right = task2.result;
merge(left, right, number);
result = number;
}
}
private void merge(int[] left, int[] right, int[] number) {
int i = 0, j = 0;
while (i < left.length || j < right.length) {
if (j >= right.length || (i < left.length && left[i] < right[j])) {
number[i + j] = left[i];
i++;
} else {
number[i + j] = right[j];
j++;
}
}
}
}
}
forkjointest.Main.java
package forkjointest;
import RandomNumber2009.RandomNumber;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
RandomNumber test = new RandomNumber();
int[] beforeNumber = test.getNumber();
System.out.println("Before");
System.out.println(Arrays.toString(beforeNumber));
MergeSort ms = new MergeSort();
int[] result = ms.sort(beforeNumber);
System.out.println("After");
System.out.println(Arrays.toString(result));
}
}
このプロジェクトを構築し、実行させると無事に動きました。
さて、ここで本当に並列化しているか確認してみます。
プロファイラを接続して ForkJoinPool のワーカースレッドが起動しているか?
プロファイラによると起動されてますね。
実行中から待機状態になり新たに4っつのワーカースレッドがありますね。
ちなみに CPU コア数は 8個なので8つのワーカースレッドが作成されます。
実行が終了し待機状態になってなんで新たにスレッドが4っつ起動されているのかは謎です。(こういう仕様なのか?)
では、生成されるワーカースレッド数を変更してみます。
int nThreads = Runtime.getRuntime().availableProcessors();
nThreads = 1;
ForkJoinPool pool = new ForkJoinPool(nThreads);
このようにワーカースレッド数を1に変更してみました。
次に 2に変更
4の場合
ちゃんと指定した数のワーカースレッドが生成されるようですね。
ついでにプロファイラの CPU でアプリケーションのパフォーマンスを調べてみようとしたところ
見れなかった(><)
ヒープを大きくしても変わらず・・・ なんでだろう?
JDK 7 に標準搭載予定の ForkJoin フレームワークについての情報はネット上にも少し流れていますので興味のあるかたはググってみてくださいね。
大切なことを忘れてました。
ノーマルのマージソートとのパフォーマンスの比較はまだしていませんので気になる方は比較してみてください。(爆

[...] 以前、「ForkJoin を NetBeans 6.8 で試してみた」でプロファイラがちゃんと動かなかったので [...]