Java用の同時クイックソートまたはマージソートアルゴリズムを実装するにはどうすればよいですか?
デフォルトのJavaソートアルゴリズムを使用して1つのコア(!)のみが機能していた16(仮想)コアMacで問題が発生しましたが、それを確認するのは良くありませんでした非常に細かいマシンは完全に十分に活用されていないので、私たちは自分で書いた(私はそれを書いた)そして確かに良いスピードアップを得た(私はマルチスレッドクイックソートを書いた、そしてそのパーティション化の性質のためにそれは非常にうまく並列化するが、マージソートも書くことができた)。 ..しかし、私の実装は4スレッドまでしか拡張できません。これは独自のコードであり、再発明されたホイールを使用する代わりに、信頼できるソースからのスレッドを使用したいと思います。
私がWebで見つけた唯一のものは、Javaでマルチスレッドクイックソートを作成する方法notの例であり、ビジーループです(これは本当にひどい)を使用して:
while (helpRequested) { }
http://broadcast.oreilly.com/2009/06/may-column-multithreaded-algor.html
したがって、理由もなく1つのスレッドを失うことに加えて、そのwhileループでビジーループを実行してパフォーマンスを強制終了するようにしています(これは気が遠くなるようなことです)。
したがって、私の質問:信頼できるソースから来るJavaの正しくマルチスレッド化されたクイックソートまたはマージソートの実装を知っていますか?
複雑さがO(n log n)のままであることを知っているという事実に重点を置きますが、アイドリングの代わりにこれらすべてのコアが機能し始めるのを見るのはとても楽しいです。他のタスクについては、同じ16個の仮想コアMacで、コードを並列化することで最大7倍のスピードアップが見られたことに注意してください(私は並行性の専門家ではありません)。
したがって、複雑さがO(n log n)のままであるとしても、x7またはx8、さらにはx16のスピードアップに感謝します。
試してみてください Doug Leaによるフレームワークのフォーク/参加 :
public class MergeSort extends RecursiveAction {
final int[] numbers;
final int startPos, endPos;
final int[] result;
private void merge(MergeSort left, MergeSort right) {
int i=0, leftPos=0, rightPos=0, leftSize = left.size(), rightSize = right.size();
while (leftPos < leftSize && rightPos < rightSize)
result[i++] = (left.result[leftPos] <= right.result[rightPos])
? left.result[leftPos++]
: right.result[rightPos++];
while (leftPos < leftSize)
result[i++] = left.result[leftPos++];
while (rightPos < rightSize)
result[i++] = right.result[rightPos++];
}
public int size() {
return endPos-startPos;
}
protected void compute() {
if (size() < SEQUENTIAL_THRESHOLD) {
System.arraycopy(numbers, startPos, result, 0, size());
Arrays.sort(result, 0, size());
} else {
int midpoint = size() / 2;
MergeSort left = new MergeSort(numbers, startPos, startPos+midpoint);
MergeSort right = new MergeSort(numbers, startPos+midpoint, endPos);
coInvoke(left, right);
merge(left, right);
}
}
}
(ソース: http://www.ibm.com/developerworks/Java/library/j-jtp03048.html?S_TACT=105AGX01&S_CMP=LP )
Java 8は Java.util.Arrays.parallelSort
、fork-joinフレームワークを使用して配列を並列にソートします。ドキュメントには、現在の実装に関する詳細が記載されています(ただし、これらは非規範的な注記です)。
並べ替えアルゴリズムは、配列をサブ配列に分割する並列並べ替えマージであり、サブ配列自体が並べ替えられてからマージされます。サブ配列の長さが最小粒度に達すると、適切なArrays.sortメソッドを使用してサブ配列が並べ替えられます。指定された配列の長さが最小粒度よりも短い場合は、適切なArrays.sortメソッドを使用して並べ替えられます。このアルゴリズムには、元の配列のサイズ以下の作業スペースが必要です。 ForkJoin共通プールは、並列タスクを実行するために使用されます。
リストに対応する並列ソート方法がないようです( RandomAccess リストはソートでうまく機能するはずですが)。したがって、toArray
を使用して、その配列をソートする必要があります。結果をリストに保存します。 (私はこれについて質問しました ここ 。)
これについては申し訳ありませんが、あなたが求めていることは不可能です。他の誰かがソートはIOバインドされており、おそらく正しいと言っていると思います。ダグ・リーによるIBMのコードは素晴らしい作品ですが、主にの例として意図されていると思います。コードの書き方。彼の記事で彼がベンチマークを投稿したことはなく、代わりに平均の計算や最小最大値の並列検索など、他の作業コードのベンチマークを投稿しました。一般的なマージを使用した場合のベンチマークは次のとおりです。ソート、クイックソート、ダグ結合フォークプールを使用したマージソート、およびクイックソート結合フォークプールを使用して作成したもの。マージソートは、Nが100以下の場合に最適であることがわかります。1000のクイックソートこれらのテストは、各データポイントの平均を作成するために30回実行されるランダムな数の配列であり、約2のクアッドコアで実行されていました。ラムのギグそして以下にクイックソートのコードがありますこれは主にuを示しています非常に大きな配列をソートしようとしているのでない限り、並列配列は小さなNで非常に遅く実行されるため、コードソートアルゴリズムを改善しようとするのはやめるべきです。
Merge Sort
10 7.51E-06
100 1.34E-04
1000 0.003286269
10000 0.023988694
100000 0.022994328
1000000 0.329776132
Quick Sort
5.13E-05
1.60E-04
7.20E-04
9.61E-04
0.01949271
0.32528383
Merge TP
1.87E-04
6.41E-04
0.003704411
0.014830678
0.019474009
0.19581768
Quick TP
2.28E-04
4.40E-04
0.002716065
0.003115251
0.014046681
0.157845389
import jsr166y.ForkJoinPool;
import jsr166y.RecursiveAction;
// derived from
// http://www.cs.princeton.edu/introcs/42sort/QuickSort.Java.html
// Copyright © 2007, Robert Sedgewick and Kevin Wayne.
// Modified for Join Fork by me hastily.
public class QuickSort {
Comparable array[];
static int limiter = 10000;
public QuickSort(Comparable array[]) {
this.array = array;
}
public void sort(ForkJoinPool pool) {
RecursiveAction start = new Partition(0, array.length - 1);
pool.invoke(start);
}
class Partition extends RecursiveAction {
int left;
int right;
Partition(int left, int right) {
this.left = left;
this.right = right;
}
public int size() {
return right - left;
}
@SuppressWarnings("empty-statement")
//void partitionTask(int left, int right) {
protected void compute() {
int i = left, j = right;
Comparable tmp;
Comparable pivot = array[(left + right) / 2];
while (i <= j) {
while (array[i].compareTo(pivot) < 0) {
i++;
}
while (array[j].compareTo(pivot) > 0) {
j--;
}
if (i <= j) {
tmp = array[i];
array[i] = array[j];
array[j] = tmp;
i++;
j--;
}
}
Partition leftTask = null;
Partition rightTask = null;
if (left < i - 1) {
leftTask = new Partition(left, i - 1);
}
if (i < right) {
rightTask = new Partition(i, right);
}
if (size() > limiter) {
if (leftTask != null && rightTask != null) {
invokeAll(leftTask, rightTask);
} else if (leftTask != null) {
invokeAll(leftTask);
} else if (rightTask != null) {
invokeAll(rightTask);
}
}else{
if (leftTask != null) {
leftTask.compute();
}
if (rightTask != null) {
rightTask.compute();
}
}
}
}
}
上記のMergeSortをコーディングしただけで、パフォーマンスが非常に悪くなりました。
コードブロックは「coInvoke(left、right);」を参照します。しかし、これへの参照はなく、invokeAll(left、right);に置き換えました。
テストコードは次のとおりです。
MergeSort mysort = new MyMergeSort(array,0,array.length);
ForkJoinPool threadPool = new ForkJoinPool();
threadPool.invoke(mysort);
しかし、パフォーマンスが悪いために停止する必要がありました。
上記の記事はほぼ1年前のものであり、状況が変わった可能性があります。
代替記事のコードが機能することがわかりました: http://blog.quibb.org/2010/03/jsr-166-the-Java-forkjoin-framework/
私はここ数日、マルチスレッドソートの問題に自分自身で直面しています。説明したように このカリフォルニア工科大学のスライドで 分割統治法の各ステップを単純にマルチスレッド化して、明らかなスレッド数(分割数)を超えてアプローチを征服することでできる最善のことは限られています。これは、マシンの64コアすべてを使用して64スレッドで64分割を実行できるのに対し、4分割は4スレッド、2対2、1対1などでしか実行できないためだと思います。したがって、多くのレベルであなたのマシンが十分に活用されていない再帰の。
昨夜、自分の仕事に役立つかもしれない解決策が思い浮かんだので、ここに投稿します。
ソート関数の最初の基準が最大サイズsの整数に基づいている場合、実際の整数または文字列内の文字であり、この整数または文字がソートの最高レベルを完全に定義するようになります。非常に高速な(そして簡単な)ソリューション。その初期整数を使用して、並べ替えの問題をより小さな並べ替えの問題に分割し、選択した標準のシングルスレッド並べ替えアルゴリズムを使用して並べ替えます。 sクラスへの分割は1回のパスで実行できると思います。クラス1のすべてがクラス2の前にソートされることをすでに知っているので、sの独立したソートを実行した後、マージの問題はありません。
例:strcmp()に基づいて並べ替えを行う場合は、文字列の最初の文字を使用してデータを256クラスに分割し、次に使用可能なスレッドで各クラスをすべて完了するまで並べ替えます。
この方法は、問題が解決するまで利用可能なすべてのコアを十分に活用しており、実装は簡単だと思います。まだ実装していないので、まだ見つけていない問題があるかもしれません。明らかに浮動小数点ソートでは機能せず、大きなsでは非効率的です。そのパフォーマンスは、クラスの定義に使用される整数/文字のエントロピーにも大きく依存します。
これは、Fabian Steegがより少ない単語で提案していたことかもしれませんが、状況によっては、大きなソートから複数の小さなソートを作成できることを明示しています。
import Java.util.Arrays;
import Java.util.concurrent.ForkJoinPool;
import Java.util.concurrent.RecursiveTask;
public class IQ1 {
public static void main(String[] args) {
// Get number of available processors
int numberOfProcessors = Runtime.getRuntime().availableProcessors();
System.out.println("Number of processors : " + numberOfProcessors);
// Input data, it can be anything e.g. log records, file records etc
long[][] input = new long[][]{
{ 5, 8, 9, 14, 20 },
{ 17, 56, 59, 80, 102 },
{ 2, 4, 7, 11, 15 },
{ 34, 37, 39, 45, 50 }
};
/* A special thread pool designed to work with fork-and-join task splitting
* The pool size is going to be based on number of cores available
*/
ForkJoinPool pool = new ForkJoinPool(numberOfProcessors);
long[] result = pool.invoke(new Merger(input, 0, input.length));
System.out.println(Arrays.toString(result));
}
/* Recursive task which returns the result
* An instance of this will be used by the ForkJoinPool to start working on the problem
* Each thread from the pool will call the compute and the problem size will reduce in each call
*/
static class Merger extends RecursiveTask<long[]>{
long[][] input;
int low;
int high;
Merger(long[][] input, int low, int high){
this.input = input;
this.low = low;
this.high = high;
}
@Override
protected long[] compute() {
long[] result = merge();
return result;
}
// Merge
private long[] merge(){
long[] result = new long[input.length * input[0].length];
int i=0;
int j=0;
int k=0;
if(high - low < 2){
return input[0];
}
// base case
if(high - low == 2){
long[] a = input[low];
long[] b = input[high-1];
result = mergeTwoSortedArrays(a, b);
}
else{
// divide the problem into smaller problems
int mid = low + (high - low) / 2;
Merger first = new Merger(input, low, mid);
Merger second = new Merger(input, mid, high);
first.fork();
long[] secondResult = second.compute();
long[] firstResult = first.join();
result = mergeTwoSortedArrays(firstResult, secondResult);
}
return result;
}
// method to merge two sorted arrays
private long[] mergeTwoSortedArrays(long[] a, long[] b){
long[] result = new long[a.length + b.length];
int i=0;
int j=0;
int k=0;
while(i<a.length && j<b.length){
if(a[i] < b[j]){
result[k] = a[i];
i++;
} else{
result[k] = b[j];
j++;
}
k++;
}
while(i<a.length){
result[k] = a[i];
i++;
k++;
}
while(j<b.length){
result[k] = b[j];
j++;
k++;
}
return result;
}
}
}
あなたはおそらくこれを考慮しましたが、より高いレベルから具体的な問題を見るのに役立つかもしれません。たとえば、1つの配列またはリストだけを並べ替えない場合は、代わりに従来のアルゴリズムを使用して個々のコレクションを同時に並べ替える方がはるかに簡単です。単一のコレクションを同時にソートしようとしています。