10個の数値を並べ替える最速の方法は?(数値は32ビットです)


211

私は問題を解決しており、10個の数値(int32)を非常に迅速にソートする必要があります。私のアプリケーションでは、10の数値を可能な限り数百万回並べ替える必要があります。私は数十億の要素のデータセットをサンプリングしていて、その中から10個の数値を選択(簡略化)してソートする必要があるたびに(ソートされた10個のエレメントリストから結論を出します)。

現在、挿入ソートを使用していますが、挿入ソートに勝る10個の数値という特定の問題に対して、非常に高速なカスタムソートアルゴリズムを実装できると思います。

この問題にどのように取り組むかについて誰かが何か考えを持っていますか?


14
言うまでもなく、一連のネストされたifステートメントが最適に機能します。ループを避けます。
ja72 2015

8
一連の順列にバイアスをかけて数値が与えられると思いますか、それとも均一に分布しますか?1つのリストの順序と次のリストの間に関係はありますか?
ダグラスザレ2015

4
データセット全体(数十億の数値を含む)はベンフォードの法則に従って配布されますが、このセットからランダムに要素を選択すると、それらはもはや存在しません(私はそう思います)。
bodacydo

13
あなたはこの読むことをお勧めしますstackoverflow.com/q/2786899/995714
phuclv

11
何十億もの要素からランダムに選択している場合、データセット全体がRAMにある場合でも、そのデータをプルするためのレイテンシが、選択した要素のソートに必要な時間よりも大きな影響を与える可能性があります。データを順次ランダムに選択するのではなく、パフォーマンスをベンチマークすることで影響をテストできます。
Steve S.

回答:


213

(HelloWorldの提案をフォローアップして、ソートネットワークを調べます。)

29-比較/スワップネットワークは、10入力ソートを実行する最も速い方法のようです。私は、1969年にWaksmanによって発見されたネットワークをJavascriptのこの例に使用しました。これは、ifステートメント、比較、スワップの単なるリストであるため、Cに直接変換する必要があります。

function sortNet10(data) {	// ten-input sorting network by Waksman, 1969
    var swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
    return(data);
}

alert(sortNet10([5,7,1,8,4,3,6,9,2,0]));

これは、独立したフェーズに分割されたネットワークのグラフィカル表現です。 並列処理を利用するには、5-4-3-4-4-4-3-2グループを4-4-4-4-4-4-3-2グループに変更します。
10入力ソーティングネットワーク(Waksman、1969)

10入力ソーティングネットワーク(Waksman、1969)を再グループ化


69
提案; スワップマクロを使用します。のようなもの#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
ピーター・コーデス

9
これが最小であることを論理的に示すことができますか?
corsiKa 2015

8
@corsiKaはい、ソートネットワークは、コンピューターサイエンスの初期の頃から研究分野でした。多くの場合、最適なソリューションは何十年も前から知られています。参照en.wikipedia.org/wiki/Sorting_network
M69「」不機嫌とunwelcoming「」

8
テストするJsperfを作成しましたが、ネットワークソートがブラウザーのネイティブソートよりも20倍以上速いことを確認できます。 jsperf.com/fastest-10-number-sort
Daniel

9
@Kataiこれは、コンパイラが生成する可能性のある最適化をすべて破壊します。悪いアイデア。より多くの情報のためにこれを読んでen.wikipedia.org/wiki/...
Antzi

88

この固定サイズを扱うときは、ソーティングネットワークご覧ください。これらのアルゴリズムはランタイムが固定されており、入力に依存しません。ユースケースでは、一部のソートアルゴリズムのようなオーバーヘッドはありません。

ビットニックソートは、このようなネットワークの実装です。これは、CPUでlen(n)<= 32を使用すると最適に機能します。より大きな入力では、GPUへの移行を考えることができます。 https://en.wikipedia.org/wiki/Sorting_network

ところで、並べ替えアルゴリズムを比較するのに適したページはこちらです(ただし、がありません)bitonic sort

http://www.sorting-algorithms.com


3
@ ErickG.Hagstrom多くの解決策があります。29の比較を使用している限り、効率は同じです。1969年からWaksmanのソリューションを使用しました。彼は明らかに29の比較バージョンを発見した最初の人でした。
m69 ''ぎこちない、歓迎されない '' 2015

1
はい、@ m69。百万以上あります。Waksmanのソリューションの長さは29、深さは9です。私がリンクしたソリューションは、深さの次元よりも改善されています。
エリックG.ハグストロム2015

4
@ ErickG.Hagstromどうやら、深さ7の87のソリューションがあり、その1つは1973年にKnuthによって発見されましたが、クイックGoogleでそれらを見つけることはできませんでした。larc.unt.edu/ian/pubs/9-input.pdf(結論、p.14を参照)
m69 '' snarky and unwelcoming ''

4
@ ErickG.Hagstrom:深さは「Cレベルで」変化をもたらさない可能性がありますが、おそらくコンパイラとCPUがそれを終了すると、CPU内で部分的に並列化される可能性があり、したがってより小さな深さが役立つ可能性があります。もちろん、CPUによって異なります。一部のCPUは比較的シンプルで、次々に処理を実行しますが、一部のCPUは複数の処理を実行できます。特に、必要なスタックへのロードとストアに対して非常に異なるパフォーマンスが得られる場合があります。それらがどのように行われるかに応じて、10個の変数を操作するための順序。
スティーブジェソップ2015

1
@ ErickG.Hagstrom Ian Parberryの論文からはすぐにはわかりませんでしたが、depth-7ネットワークの長さは29を超えています。Knuthの「The Art Of Computer Programming Vol.III」、§5.3.4、図を参照してください。 。49と51
M69「」不機嫌とunwelcoming「」

33

SIMDレジスターで実行できるように、4つのグループで比較するソートネットワークを使用します。パックされた最小/最大命令のペアは、パックされたコンパレータ機能を実装します。申し訳ありませんが、これについて覚えているページを探す時間はありませんが、うまくいけば、SIMDまたはSSEのソートネットワークで検索すると、何かが表示されます。

x86 SSEには、4つの32ビット整数のベクトルに対するパックされた32ビット整数の最小および最大命令があります。AVX2(Haswell以降)も同じですが、8ビットの256bベクトル用です。効率的なシャッフルの説明もあります。

独立した小さな並べ替えが多数ある場合は、ベクトルを使用して4または8の並べ替えを並行して実行できる可能性があります。特に 要素をランダムに選択する場合(並べ替えられるデータがメモリ内で連続しないようにするため)、シャッフルを回避し、必要な順序で単純に比較できます。10個の整数の4(AVX2:8)リストからのすべてのデータを保持するための10個のレジスタは、スクラッチスペース用に6個のレジスタを残します。

関連付けられたデータもソートする必要がある場合、ベクトルソートネットワークは効率が低下します。その場合、最も効率的な方法は、パックされた比較を使用して、変更された要素のマスクを取得し、そのマスクを使用して関連データ(への参照)のベクトルをブレンドすることです。


26

アンロールされたブランチのない選択ソートはどうですか?

#include <iostream>
#include <algorithm>
#include <random>

//return the index of the minimum element in array a
int min(const int * const a) {
  int m = a[0];
  int indx = 0;
  #define TEST(i) (m > a[i]) && (m = a[i], indx = i ); 
  //see http://stackoverflow.com/a/7074042/2140449
  TEST(1);
  TEST(2);
  TEST(3);
  TEST(4);
  TEST(5);
  TEST(6);
  TEST(7);
  TEST(8);
  TEST(9);
  #undef TEST
  return indx;
}

void sort( int * const a ){
  int work[10];
  int indx;
  #define GET(i) indx = min(a); work[i] = a[indx]; a[indx] = 2147483647; 
  //get the minimum, copy it to work and set it at max_int in a
  GET(0);
  GET(1);
  GET(2);
  GET(3);
  GET(4);
  GET(5);
  GET(6);
  GET(7);
  GET(8);
  GET(9);
  #undef GET
  #define COPY(i) a[i] = work[i];
  //copy back to a
  COPY(0);
  COPY(1);
  COPY(2);
  COPY(3);
  COPY(4);
  COPY(5);
  COPY(6);
  COPY(7);
  COPY(8);
  COPY(9);
  #undef COPY
}

int main() {
  //generating and printing a random array
  int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
  std::random_device rd;
  std::mt19937 g(rd());
  std::shuffle( a, a+10, g);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  }
  std::cout << std::endl;

  //sorting and printing again
  sort(a);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  } 

  return 0;
}

http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6

関連する行は最初の2行だけ#defineです。

2つのリストを使用し、最初のリストを10回完全に再チェックします。これは不適切に実装された選択ソートですが、ブランチや可変長ループを回避します。これにより、最新のプロセッサやそのような小さなデータセットで補正できる場合があります。


基準

並べ替えネットワークに対してベンチマークを実行したところ、コードが遅くなったようです。ただし、展開とコピーを削除しようとしました。このコードを実行する:

#include <iostream>
#include <algorithm>
#include <random>
#include <chrono>

int min(const int * const a, int i) {
  int m = a[i];
  int indx = i++;
  for ( ; i<10; i++) 
    //see http://stackoverflow.com/a/7074042/2140449
    (m > a[i]) && (m = a[i], indx = i ); 
  return indx;
}

void sort( int * const a ){
  for (int i = 0; i<9; i++)
    std::swap(a[i], a[min(a,i)]); //search only forward
}


void sortNet10(int * const data) {  // ten-input sorting network by Waksman, 1969
    int swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
}


std::chrono::duration<double> benchmark( void(*func)(int * const), const int seed ) {
  std::mt19937 g(seed);
  int a[10] = {10,11,12,13,14,15,16,17,18,19};
  std::chrono::high_resolution_clock::time_point t1, t2; 
  t1 = std::chrono::high_resolution_clock::now();
  for (long i = 0; i < 1e7; i++) {
    std::shuffle( a, a+10, g);
    func(a);
  }
  t2 = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
}

int main() {
  std::random_device rd;
  for (int i = 0; i < 10; i++) {
    const int seed = rd();
    std::cout << "seed = " << seed << std::endl;
    std::cout << "sortNet10: " << benchmark(sortNet10, seed).count() << std::endl;
    std::cout << "sort:      " << benchmark(sort,      seed).count() << std::endl;
  }
  return 0;
}

並べ替えネットワークと比較して、ブランチのない選択並べ替えの方が常に良い結果を得ています。

$ gcc -v
gcc version 5.2.0 (GCC) 
$ g++ -std=c++11 -Ofast sort.cpp && ./a.out
seed = -1727396418
sortNet10: 2.24137
sort:      2.21828
seed = 2003959850
sortNet10: 2.23914
sort:      2.21641
seed = 1994540383
sortNet10: 2.23782
sort:      2.21778
seed = 1258259982
sortNet10: 2.25199
sort:      2.21801
seed = 1821086932
sortNet10: 2.25535
sort:      2.2173
seed = 412262735
sortNet10: 2.24489
sort:      2.21776
seed = 1059795817
sortNet10: 2.29226
sort:      2.21777
seed = -188551272
sortNet10: 2.23803
sort:      2.22996
seed = 1043757247
sortNet10: 2.2503
sort:      2.23604
seed = -268332483
sortNet10: 2.24455
sort:      2.24304

4
結果はそれほど印象的ではありませんが、実際には私が期待していたものです。並べ替えネットワークは、スワップではなく比較を最小限に抑えます。すべての値が既にキャッシュにある場合、比較はスワップよりもはるかに安価であるため、選択ソート(スワップの数を最小限に抑える)が優先されます。(そして、それほど多くの比較はありません:29の同情を伴うネットワーク、最大29のスワップ?;対45の比較を伴う選択ソート、最大で9のスワップ)

7
ああ、それはブランチを持っています-ラインfor ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i ); が非常によく最適化されていない限り。(短絡は通常、分岐の形式です)

1
@EugeneRyabtsevも同じですが、常にまったく同じランダムシーケンスが供給されるため、キャンセルする必要があります。と変えstd::shuffleてみましたfor (int n = 0; n<10; n++) a[n]=g();。実行時間が半分になり、ネットワークが高速になりました。
DarioP 2015

これはlibc ++とどのように比較されstd::sortますか?
gnzlbg 2015

1
@gnzlbg私も試しstd::sortましたが、パフォーマンスが非常に悪かったため、ベンチマークに含めていませんでした。小さなデータセットではかなりのオーバーヘッドがあると思います。
DarioP、2015

20

質問は、これがWebベースのアプリケーションの一種であるとは言いません。私の目に留まったのは、

私は数十億の要素のデータセットをサンプリングしていて、その中から10個の数値を選択(簡略化)してソートする必要があるたびに(ソートされた10個のエレメントリストから結論を出します)。

ソフトウェアおよびハードウェアエンジニアとして、これは"FPGA"を私に絶叫します。並べ替えられた数値のセットからどのような結論を引き出す必要があるのか​​、またはデータがどこから来ているのかはわかりませんが、1億から10億の間のこれらの「並べ替え処理」のどこかで処理することはほとんど簡単です。1秒あたりの操作。私は過去にFPGA支援のDNAシーケンス作業を行ったことがあります。問題がそのタイプのソリューションに適している場合、FPGAの大規模な処理能力を打ち負かすことはほぼ不可能です。

あるレベルでの唯一の制限要因は、FPGAへのデータの入力速度と出力速度です。

参考までに、毎秒約3億ピクセルの速度で32ビットRGB画像データを受信する高性能リアルタイム画像プロセッサを設計しました。FIRフィルター、行列乗算器、ルックアップテーブル、空間エッジ検出ブロック、および他の多くの演算を通じてストリーミングされたデータは、相手側に出ます。比較的小さなザイリンクスVirtex2 FPGAでのこれらすべては、約33MHzから、私が正しく覚えていれば400MHzまでの内部クロックを備えています。ああ、はい、それはDDR2コントローラーの実装も備えており、DDR2メモリの2つのバンクを実行していました。

FPGAは、数百MHzで動作しながら、クロック遷移ごとに10種類の32ビット数を出力できます。データが処理パイプラインを満たしているため、操作の開始時に短い遅延が発生します。その後、クロックごとに1つの結果を取得できるはずです。並べ替えと分析のパイプラインを複製することで処理を並列化できる場合は、さらに多くのことを行います。原則として、解決策はほとんど取るに足らないものです。

重要なのは、アプリケーションがPCにバインドされておらず、データストリームと処理がFPGAソリューションと互換性がある(スタンドアロンまたはマシンのコプロセッサカードとして)場合は、方法がないことです。アルゴリズムに関係なく、任意の言語で記述されたソフトウェアを使用して、達成可能なパフォーマンスレベルを超えることができます。

編集:

クイック検索を実行して、役に立つかもしれない論文を見つけました。それは2012年にさかのぼるように見えます。今日(さらにはそれでも)、LOTのパフォーマンスを向上させることができます。ここにあります:

FPGA上のネットワークの並べ替え


10

私は最近、Bose-Nelsonアルゴリズムを使用してコンパイル時にソートネットワークを生成する小さなクラスを書きました。

10個の数値に対して非常に高速なソートを作成するために使用できます。

/**
 * A Functor class to create a sort for fixed sized arrays/containers with a
 * compile time generated Bose-Nelson sorting network.
 * \tparam NumElements  The number of elements in the array or container to sort.
 * \tparam T            The element type.
 * \tparam Compare      A comparator functor class that returns true if lhs < rhs.
 */
template <unsigned NumElements, class Compare = void> class StaticSort
{
    template <class A, class C> struct Swap
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            T t = Compare()(v0, v1) ? v0 : v1; // Min
            v1 = Compare()(v0, v1) ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A> struct Swap <A, void>
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            // Explicitly code out the Min and Max to nudge the compiler
            // to generate branchless code.
            T t = v0 < v1 ? v0 : v1; // Min
            v1 = v0 < v1 ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A, class C, int I, int J, int X, int Y> struct PB
    {
        inline PB(A &a)
        {
            enum { L = X >> 1, M = (X & 1 ? Y : Y + 1) >> 1, IAddL = I + L, XSubL = X - L };
            PB<A, C, I, J, L, M> p0(a);
            PB<A, C, IAddL, J + M, XSubL, Y - M> p1(a);
            PB<A, C, IAddL, J, XSubL, M> p2(a);
        }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 1>
    {
        inline PB(A &a) { Swap<A, C> s(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 2>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J); Swap<A, C> s1(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 2, 1>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J - 1); Swap<A, C> s1(a, I, J - 1); }
    };

    template <class A, class C, int I, int M, bool Stop = false> struct PS
    {
        inline PS(A &a)
        {
            enum { L = M >> 1, IAddL = I + L, MSubL = M - L};
            PS<A, C, I, L, (L <= 1)> ps0(a);
            PS<A, C, IAddL, MSubL, (MSubL <= 1)> ps1(a);
            PB<A, C, I, IAddL, L, MSubL> pb(a);
        }
    };

    template <class A, class C, int I, int M> struct PS <A, C, I, M, true>
    {
        inline PS(A &a) {}
    };

public:
    /**
     * Sorts the array/container arr.
     * \param  arr  The array/container to be sorted.
     */
    template <class Container> inline void operator() (Container &arr) const
    {
        PS<Container, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };

    /**
     * Sorts the array arr.
     * \param  arr  The array to be sorted.
     */
    template <class T> inline void operator() (T *arr) const
    {
        PS<T*, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };
};

#include <iostream>
#include <vector>

int main(int argc, const char * argv[])
{
    enum { NumValues = 10 };

    // Arrays
    {
        int rands[NumValues];
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    std::cout << "\n";

    // STL Vector
    {
        std::vector<int> rands(NumValues);
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    return 0;
}

if (compare) swapステートメントの代わりに、minおよびmaxの3項演算子を明示的にコーディングしていることに注意してください。これは、コンパイラーが分岐のないコードを使用するように調整するのに役立ちます。

ベンチマーク

以下のベンチマークはclang -O3でコンパイルされ、2012年中頃のMacBook Airで実行されました。

ランダムデータの並べ替え

DarioPのコードと比較すると、サイズ10の100万個の32ビットint配列を並べ替えるのにかかるミリ秒数は次のとおりです。

ハードコードされたソートネット10: 88.774 ms
テンプレート化されたBose-Nelsonソート10: 27.815 ms

このテンプレート化されたアプローチを使用して、他の数の要素のコンパイル時にソートネットワークを生成することもできます。

さまざまなサイズの100万配列をソートする時間(ミリ秒)。
サイズ2、4、8の配列のミリ秒数は、それぞれ1.943、8.655、20.246です。
C ++テンプレート化されたBose-Nelson静的ソートのタイミング

クレジットグレンTeitelbaum広げられた挿入ソートのために。

以下は、6要素の小さな配列のソートごとの平均クロックです。ベンチマークコードと例は、この質問で見つけることができます:
最速の固定長6整数配列

Direct call to qsort library function       : 326.81
Naive implementation (insertion sort)       : 132.98
Insertion Sort (Daniel Stutzbach)           : 104.04
Insertion Sort Unrolled                     : 99.64
Insertion Sort Unrolled (Glenn Teitelbaum)  : 81.55
Rank Order                                  : 44.01
Rank Order with registers                   : 42.40
Sorting Networks (Daniel Stutzbach)         : 88.06
Sorting Networks (Paul R)                   : 31.64
Sorting Networks 12 with Fast Swap          : 29.68
Sorting Networks 12 reordered Swap          : 28.61
Reordered Sorting Network w/ fast swap      : 24.63
Templated Sorting Network (this class)      : 25.37

それは、6つの要素についての質問で最も速い例と同じくらい速く実行されます。

ソートされたデータのソートのパフォーマンス

多くの場合、入力配列は既にソートされているか、ほとんどソートされている可能性があります。
このような場合、挿入ソートの方が適しています。

ここに画像の説明を入力してください

データに応じて適切な並べ替えアルゴリズムを選択することができます。

ベンチマークに使用されたコードはここにあります


以下の私のアルゴの比較を追加できますか?
Glenn Teitelbaum 2016年

@GlennTeitelbaum ベンチマークにこれを追加し手段と結果を開示した可能性はありますか?
greybeard 2016年

ソートされた入力のソートに関するデータの追加に対する称賛。
greybeard '25年

いくつかのシステムではv1 = v0 < v1 ? v1 : v0; // Maxまだ5月の枝、その場合にはそれはと交換することができるv1 += v0 - tならばためtv0、その後v1 + v0 -t == v1 + v0 - v0 == v1、他がtあるv1v1 + v0 -t == v1 + v0 - v1 == v0
グレンTeitelbaum

三元は、通常、最新のコンパイラではmaxssor minss命令にコンパイルされます。ただし、機能しない場合は、他のスワッピング方法を使用できます。:)
2016年

5

ネットワークの並べ替えは小さな配列で高速である可能性が高いですが、適切に最適化されている場合、挿入の並べ替えに勝てないことがあります。たとえば、2つの要素を持つバッチ挿入:

{
    final int a=in[0]<in[1]?in[0]:in[1];
    final int b=in[0]<in[1]?in[1]:in[0];
    in[0]=a;
    in[1]=b;
}
for(int x=2;x<10;x+=2)
{
    final int a=in[x]<in[x+1]?in[x]:in[x+1];
    final int b=in[x]<in[x+1]?in[x+1]:in[x];
    int y= x-1;

    while(y>=0&&in[y]>b)
    {
        in[y+2]= in[y];
        --y;
    }
    in[y+2]=b;
    while(y>=0&&in[y]>a)
    {
        in[y+1]= in[y];
        --y;
    }
    in[y+1]=a;
}

なぜin[y+2]= in[y];タイプミスを繰り返すのかわからない?
Glenn Teitelbaum 2016年

わあ、どうやったの?そして、誰かが気づくまでにどのくらいの時間がかかりましたか?答え:それはタイプミスではありません。私はキーと値の配列の両方を持つ別のアルゴリズムを採用していました。
ウォーレン

3

完全に展開できます insertion sort

これを簡単にするために、template関数のオーバーヘッドなしで再帰的なを使用できます。すでになので、パラメータtemplateintすることもできますtemplate。これにより、10以外の配列サイズのコーディングも簡単に作成できます。

クラスが最後のアイテムのインデックスを使用するためint x[10]、呼び出しをソートすることに注意してくださいinsert_sort<int, 9>::sort(x);。これはラップすることもできますが、読み通すコードが多くなります。

template <class T, int NUM>
class insert_sort;

template <class T>
class insert_sort<T,0>
// stop template recursion
// sorting 1 item is a no-op
{
public:
    static void place(T *x) {}
    static void sort(T * x) {}
};

template <class T, int NUM>
class insert_sort
// use template recursion to do insertion sort
// NUM is the index of the last item, eg. for x[10] call <9>
{
public:
    static void place(T *x)
    {
        T t1=x[NUM-1];
        T t2=x[NUM];
        if (t1 > t2)
        {
            x[NUM-1]=t2;
            x[NUM]=t1;
            insert_sort<T,NUM-1>::place(x);
        }
    }
    static void sort(T * x)
    {
        insert_sort<T,NUM-1>::sort(x); // sort everything before
        place(x);                    // put this item in
    }
};

私のテストでは、これはソーティングネットワークの例よりも高速でした。


0

私が述べたことと同様の理由から、ここで、以下のソート機能を、sort6_iterator()そしてsort10_iterator_local()、うまく実行する必要があり、ソーティングネットワークから撮影された場所をここに

template<class IterType> 
inline void sort10_iterator(IterType it) 
{
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a)   auto data##a=*(data+a);
#define DD2(a,b) auto data##a=*(data+a), data##b=*(data+b);
#define CB1(a)   *(data+a)=data##a;
#define CB2(a,b) *(data+a)=data##a;*(data+b)=data##b;
  DD2(1,4) SORT2(1,4) DD2(7,8) SORT2(7,8) DD2(2,3) SORT2(2,3) DD2(5,6) SORT2(5,6) DD2(0,9) SORT2(0,9) 
  SORT2(2,5) SORT2(0,7) SORT2(8,9) SORT2(3,6) 
  SORT2(4,9) SORT2(0,1) 
  SORT2(0,2) CB1(0) SORT2(6,9) CB1(9) SORT2(3,5) SORT2(4,7) SORT2(1,8) 
  SORT2(3,4) SORT2(5,8) SORT2(6,7) SORT2(1,2) 
  SORT2(7,8) CB1(8) SORT2(1,3) CB1(1) SORT2(2,5) SORT2(4,6) 
  SORT2(2,3) CB1(2) SORT2(6,7) CB1(7) SORT2(4,5) 
  SORT2(3,4) CB2(3,4) SORT2(5,6) CB2(5,6) 
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

この関数を呼び出すために、std::vectorイテレータを渡しました。


0

挿入の並べ替えでは、10の入力を並べ替えるのに平均で29,6の比較が必要であり、ベストケースは9で最悪の場合は45です(逆順の所定の入力)。

{9,6,1}シェルソートでは、10個の入力をソートするために平均25.5回の比較が必要になります。最良の場合は14の比較で、最悪の場合は34で、逆の入力をソートするには22が必要です。

したがって、挿入ソートの代わりにシェルソートを使用すると、平均ケースが14%減少します。最良のケースは56%増加しますが、最悪のケースは24%減少します。これは、最悪のケースのパフォーマンスを維持することが重要なアプリケーションでは重要です。逆の場合は51%削減されます。

挿入ソートに慣れているようなので、アルゴリズムを{9,6}のソートネットワークとして実装し、その後挿入ソート({1})を追加できます。

i[0] with i[9]    // {9}

i[0] with i[6]    // {6}
i[1] with i[7]    // {6}
i[2] with i[8]    // {6}
i[3] with i[9]    // {6}

i[0 ... 9]        // insertion sort
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.