412

別のStackOverflowの質問(これ)に答えると、興味深いサブ問題に遭遇しました。6つの整数の配列をソートする最速の方法は何ですか?

質問は非常に低いレベルなので:

  • ライブラリが利用可能であると想定することはできません(そして呼び出し自体にコストがかかります)。プレーンCのみです。
  • 命令パイプライン(非常に高いコストがかかる)を空にすることを避けるために、おそらく分岐、ジャンプ、および他のすべての種類の制御フローの中断(&&またはのシーケンスポイントの背後に隠されているものなど)を最小限に抑える必要があり||ます。
  • 部屋には制約があり、レジスタとメモリの使用を最小限に抑えることが問題です。理想的には、インプレースソートがおそらく最善です。

本当にこの質問は、ソースの長さを最小化するのではなく、実行時間を最小化することが目標である一種のゴルフです。マイケル・アブラッシュとその続編による「コードの最適化の禅」という本のタイトルで使用されているように、私はそれを「Zening」コードと呼んでいます。

それが興味深い理由については、いくつかの層があります。

  • この例は単純で、理解と測定が簡単で、Cスキルはあまり必要ありません。
  • 問題に適したアルゴリズムを選択した場合の影響だけでなく、コンパイラと基盤となるハードウェアの影響も示しています。

これが私のリファレンス(ナイーブ、最適化されていない)の実装と私のテストセットです。

#include <stdio.h>

static __inline__ int sort6(int * d){

    char j, i, imin;
    int tmp;
    for (j = 0 ; j < 5 ; j++){
        imin = j;
        for (i = j + 1; i < 6 ; i++){
            if (d[i] < d[imin]){
                imin = i;
            }
        }
        tmp = d[j];
        d[j] = d[imin];
        d[imin] = tmp;
    }
}

static __inline__ unsigned long long rdtsc(void)
{
  unsigned long long int x;
     __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
     return x;
}

int main(int argc, char ** argv){
    int i;
    int d[6][5] = {
        {1, 2, 3, 4, 5, 6},
        {6, 5, 4, 3, 2, 1},
        {100, 2, 300, 4, 500, 6},
        {100, 2, 3, 4, 500, 6},
        {1, 200, 3, 4, 5, 600},
        {1, 1, 2, 1, 2, 1}
    };

    unsigned long long cycles = rdtsc();
    for (i = 0; i < 6 ; i++){
        sort6(d[i]);
        /*
         * printf("d%d : %d %d %d %d %d %d\n", i,
         *  d[i][0], d[i][6], d[i][7],
         *  d[i][8], d[i][9], d[i][10]);
        */
    }
    cycles = rdtsc() - cycles;
    printf("Time is %d\n", (unsigned)cycles);
}

生の結果

バリアントの数が増えているので、ここにあるテストスイートにそれらをすべて集めました。使用された実際のテストは、Kevin Stockのおかげで、上に示したものよりも少し単純ではありません。独自の環境でコンパイルして実行できます。さまざまなターゲットアーキテクチャ/コンパイラでの動作に非常に興味があります。(OKみんな、答えに入れてください、私は新しい結果セットのすべての貢献者を+1します)。

私は1年前にダニエル・スタッツバッハ(ゴルフ用)に答えました。彼は当時最速のソリューション(ソーティングネットワーク)のソースでした。

Linux 64ビット、gcc 4.6.1 64ビット、Intel Core 2 Duo E8400、-O2

  • qsortライブラリ関数への直接呼び出し:689.38
  • ナイーブな実装(挿入ソート):285.70
  • 挿入ソート(Daniel Stutzbach):142.12
  • 挿入ソート展開:125.47
  • ランク順:102.26
  • レジスター付きランク順:58.03
  • ソーティングネットワーク(Daniel Stutzbach):111.68
  • ソーティングネットワーク(Paul R):66.36
  • 高速スワップを使用したネットワーク12の並べ替え:58.86
  • ソーティングネットワーク12の並べ替えスワップ:53.74
  • ソーティングネットワーク12はSimpleSwapを並べ替えました:31.54
  • 高速スワップ付きの並べ替えられたソーティングネットワーク:31.54
  • 高速スワップV2を使用した並べ替えネットワーク:33.63
  • インラインバブルソート(Paolo Bonzini):48.85
  • 展開された挿入ソート(Paolo Bonzini):75.30

Linux 64ビット、gcc 4.6.1 64ビット、Intel Core 2 Duo E8400、-O1

  • qsortライブラリ関数への直接呼び出し:705.93
  • ナイーブな実装(挿入ソート):135.60
  • 挿入ソート(Daniel Stutzbach):142.11
  • 挿入ソート展開:126.75
  • ランク順:46.42
  • レジスター付きのランク順:43.58
  • ソーティングネットワーク(Daniel Stutzbach):115.57
  • ソーティングネットワーク(Paul R):64.44
  • 高速スワップを使用したネットワーク12の並べ替え:61.98
  • ソーティングネットワーク12の並べ替えスワップ:54.67
  • ソーティングネットワーク12はSimpleSwapを並べ替えました:31.54
  • 高速スワップ付きの並べ替えられたソーティングネットワーク:31.24
  • 高速スワップV2を使用した並べ替えネットワーク:33.07
  • インラインバブルソート(Paolo Bonzini):45.79
  • 展開された挿入ソート(Paolo Bonzini):80.15

驚くべきことに、いくつかのプログラムではO2の効率がO1よりも低いため、-O1と-O2の両方の結果を含めました。どのような特定の最適化がこの効果をもたらすのだろうか?

提案されたソリューションに関するコメント

挿入ソート(Daniel Stutzbach)

予想通り、ブランチを最小化することは確かに良い考えです。

ソーティングネットワーク(Daniel Stutzbach)

挿入ソートよりも優れています。主な効果は外部ループを回避することから得られたのではないかと思いました。挿入ソートを展開して確認してみたところ、ほぼ同じ数値が得られました(コードはこちら)。

ソーティングネットワーク(Paul R)

これまでで最高。私がテストに使用した実際のコードはここにあります。他のソーティングネットワークの実装のほぼ2倍の速度である理由はまだわかりません。パラメータの受け渡し?ファストマックス?

高速スワップを使用したネットワーク12SWAPの並べ替え

Daniel Stutzbachが提案したように、私は彼の12スワップソーティングネットワークをブランチレス高速スワップと組み合わせました(コードはここにあります)。それは確かに高速であり、1つ少ないスワップを使用して期待できるように、わずかなマージン(約5%)でこれまでのところ最高です。

ブランチレススワップは、PPCアーキテクチャでifを使用する単純なスワップよりもはるかに(4倍)効率が低いように見えることにも注目してください。

ライブラリqsortの呼び出し

別の参照ポイントを与えるために、私は提案されたようにライブラリqsortを呼び出すことも試みました(コードはここにあります)。予想どおり、はるかに遅くなります。10〜30倍遅くなります...新しいテストスイートで明らかになったように、主な問題は最初の呼び出し後のライブラリの初期ロードであるように見え、他のライブラリと比べてもそれほど悪くはありません。バージョン。私のLinuxでは3倍から20倍遅いです。他の人がテストに使用する一部のアーキテクチャでは、さらに高速に見えるようです(ライブラリqsortはより複雑なAPIを使用しているため、このアーキテクチャには本当に驚いています)。

順位

Rex Kerrは、まったく異なる別の方法を提案しました。配列の各項目について、その最終位置を直接計算します。ランク順の計算には分岐が必要ないため、これは効率的です。この方法の欠点は、配列の3倍のメモリ量(ランク順を格納するための配列と変数の1つのコピー)を必要とすることです。パフォーマンスの結果は非常に驚くべきものです(そして興味深いものです)。32ビットOSとIntelCore2Quad E8300を使用したリファレンスアーキテクチャでは、サイクル数は1000をわずかに下回りました(分岐スワップを使用したネットワークの並べ替えなど)。しかし、64ビットボックス(Intel Core2 Duo)でコンパイルして実行すると、パフォーマンスが大幅に向上しました。これまでのところ最速になりました。私はついに本当の理由を見つけました。私の32ビットボックスはgcc4.4.1を使用し、64ビットボックスはgcc4.4を使用します。

更新

上記の公開された図が示すように、この効果はgccの新しいバージョンによってさらに強化され、ランク順は他の代替手段の2倍の速度になりました。

並べ替えられたスワップを使用したネットワーク12の並べ替え

gcc4.4.3を使用したRexKerr提案の驚くべき効率は、私に不思議に思いました。メモリ使用量が3倍のプログラムは、ブランチレスソーティングネットワークよりもどのように高速でしょうか。私の仮説は、書き込み後に読み取られる種類の依存関係が少なく、x86のスーパースカラー命令スケジューラをより適切に使用できるようにするというものでした。それは私にアイデアを与えました:書き込み後の読み取り依存関係を最小化するためにスワップを並べ替えます。もっと簡単に言えSWAP(1, 2); SWAP(0, 2);ば、両方が共通のメモリセルにアクセスするため、最初のスワップが終了するのを待ってから2番目のスワップを実行する必要があります。これを行うとSWAP(1, 2); SWAP(4, 5);、プロセッサは両方を並行して実行できます。私はそれを試しましたが、期待どおりに機能し、ソーティングネットワークは約10%高速に実行されています。

単純なスワップを使用したネットワーク12の並べ替え

Steinar H. Gundersonが最初の投稿から1年後、コンパイラーの裏をかくことを試みて、スワップコードを単純に保つべきではないと提案しました。結果のコードは約40%速いので、それは確かに良い考えです!彼はまた、x86インラインアセンブリコードを使用して手動で最適化されたスワップを提案しました。最も驚くべきことは(プログラマーの心理学に関するボリュームを示しています)、1年前にそのバージョンのスワップを試した人は誰もいなかったことです。私がテストに使用したコードはここにあります。他の人は、C高速スワップを書く他の方法を提案しましたが、それはまともなコンパイラを備えた単純なものと同じパフォーマンスをもたらします。

「最良の」コードは次のとおりです。

static inline void sort6_sorting_network_simple_swap(int * d){
#define min(x, y) (x<y?x:y)
#define max(x, y) (x<y?y:x) 
#define SWAP(x,y) { const int a = min(d[x], d[y]); \
                    const int b = max(d[x], d[y]); \
                    d[x] = a; d[y] = b; }
    SWAP(1, 2);
    SWAP(4, 5);
    SWAP(0, 2);
    SWAP(3, 5);
    SWAP(0, 1);
    SWAP(3, 4);
    SWAP(1, 4);
    SWAP(0, 3);
    SWAP(2, 5);
    SWAP(1, 3);
    SWAP(2, 4);
    SWAP(2, 3);
#undef SWAP
#undef min
#undef max
}

私たちのテストセットを信じるなら(そして、はい、それはかなり貧弱です、それは私たちが測定しているものを短く、単純で理解しやすいという単なる利点です)、1つのソートの結果のコードの平均サイクル数は40サイクル未満です( 6つのテストが実行されます)。これにより、各スワップは平均4サイクルになります。私はそれを驚くほど速く呼んでいます。他に可能な改善はありますか?

4

24 に答える 24

165

最適化の場合は、常にテスト、テスト、テストを行うのが最善です。少なくともネットワークのソートと挿入ソートを試してみます。賭けていたら、過去の経験に基づいて挿入ソートにお金をかけていました。

入力データについて何か知っていますか?一部のアルゴリズムは、特定の種類のデータでパフォーマンスが向上します。たとえば、挿入ソートは、ソートされたデータまたはほぼソートされたデータでパフォーマンスが向上するため、ほぼソートされたデータの可能性が平均を上回っている場合に適しています。

あなたが投稿したアルゴリズムは挿入ソートに似ていますが、より多くの比較を犠牲にしてスワップの数を最小限に抑えたようです。ただし、ブランチによって命令パイプラインが停止する可能性があるため、比較はスワップよりもはるかにコストがかかります。

挿入ソートの実装は次のとおりです。

static __inline__ int sort6(int *d){
        int i, j;
        for (i = 1; i < 6; i++) {
                int tmp = d[i];
                for (j = i; j >= 1 && tmp < d[j-1]; j--)
                        d[j] = d[j-1];
                d[j] = tmp;
        }
}

これが私がソーティングネットワークを構築する方法です。まず、このサイトを使用して、適切な長さのネットワーク用のSWAPマクロの最小セットを生成します。それを関数にまとめると、次のようになります。

static __inline__ int sort6(int * d){
#define SWAP(x,y) if (d[y] < d[x]) { int tmp = d[x]; d[x] = d[y]; d[y] = tmp; }
    SWAP(1, 2);
    SWAP(0, 2);
    SWAP(0, 1);
    SWAP(4, 5);
    SWAP(3, 5);
    SWAP(3, 4);
    SWAP(0, 3);
    SWAP(1, 4);
    SWAP(2, 5);
    SWAP(2, 4);
    SWAP(1, 3);
    SWAP(2, 3);
#undef SWAP
}
于 2010-05-07T15:02:00.970 に答える
65

ソーティングネットワークを使用した実装は次のとおりです。

inline void Sort2(int *p0, int *p1)
{
    const int temp = min(*p0, *p1);
    *p1 = max(*p0, *p1);
    *p0 = temp;
}

inline void Sort3(int *p0, int *p1, int *p2)
{
    Sort2(p0, p1);
    Sort2(p1, p2);
    Sort2(p0, p1);
}

inline void Sort4(int *p0, int *p1, int *p2, int *p3)
{
    Sort2(p0, p1);
    Sort2(p2, p3);
    Sort2(p0, p2);  
    Sort2(p1, p3);  
    Sort2(p1, p2);  
}

inline void Sort6(int *p0, int *p1, int *p2, int *p3, int *p4, int *p5)
{
    Sort3(p0, p1, p2);
    Sort3(p3, p4, p5);
    Sort2(p0, p3);  
    Sort2(p2, p5);  
    Sort4(p1, p2, p3, p4);  
}

minこれには、非常に効率的なブランチレスと実装が本当に必要です。maxこれは、このコードが事実上、一連のmin操作max(合計で13個)に要約されるためです。これは読者の練習問題として残しておきます。

この実装は、ベクトル化(たとえば、SIMD-ほとんどのSIMD ISAにはベクトルの最小/最大命令があります)およびGPU実装(たとえば、CUDA-ブランチレスであるため、ワープの発散などの問題はありません)に適していることに注意してください。

参照:非常に小さなリストをソートするための高速アルゴリズムの実装

于 2010-05-07T07:37:30.750 に答える
47

これらは整数であり、比較が高速であるため、それぞれのランク順を直接計算してみませんか。

inline void sort6(int *d) {
  int e[6];
  memcpy(e,d,6*sizeof(int));
  int o0 = (d[0]>d[1])+(d[0]>d[2])+(d[0]>d[3])+(d[0]>d[4])+(d[0]>d[5]);
  int o1 = (d[1]>=d[0])+(d[1]>d[2])+(d[1]>d[3])+(d[1]>d[4])+(d[1]>d[5]);
  int o2 = (d[2]>=d[0])+(d[2]>=d[1])+(d[2]>d[3])+(d[2]>d[4])+(d[2]>d[5]);
  int o3 = (d[3]>=d[0])+(d[3]>=d[1])+(d[3]>=d[2])+(d[3]>d[4])+(d[3]>d[5]);
  int o4 = (d[4]>=d[0])+(d[4]>=d[1])+(d[4]>=d[2])+(d[4]>=d[3])+(d[4]>d[5]);
  int o5 = 15-(o0+o1+o2+o3+o4);
  d[o0]=e[0]; d[o1]=e[1]; d[o2]=e[2]; d[o3]=e[3]; d[o4]=e[4]; d[o5]=e[5];
}
于 2010-05-07T23:19:00.370 に答える
35

一年遅れてパーティーに来たようですが、ここに行きます...

gcc 4.5.2によって生成されたアセンブリを見ると、スワップごとにロードとストアが実行されていることがわかりました。これは実際には必要ありません。6つの値をレジスタにロードし、それらをソートして、メモリに格納することをお勧めします。私は店で荷物をできるだけ近くに置くように注文しました。レジスターが最初に必要になり、最後に使用されます。SteinarH.GundersonのSWAPマクロも使用しました。更新:Paolo BonziniのSWAPマクロに切り替えました。gccはGundersonに似たものに変換しますが、gccは明示的なアセンブリとして指定されていないため、命令をより適切に順序付けることができます。

より良い順序があるかもしれませんが、私は最高のパフォーマンスとして与えられた再注文されたスワップネットワークと同じスワップ順序を使用しました。もう少し時間があれば、たくさんの順列を生成してテストします。

テストコードを変更して、4000を超える配列を検討し、各配列を並べ替えるのに必要な平均サイクル数を示しました。i5-650では、元の並べ替えられたソーティングネットワークが〜65.3サイクル/ソート(-O1、ビート-O2、-O3)を取得しているのに対し、〜34.1サイクル/ソート(-O3を使用)を取得しています。

#include <stdio.h>

static inline void sort6_fast(int * d) {
#define SWAP(x,y) { int dx = x, dy = y, tmp; tmp = x = dx < dy ? dx : dy; y ^= dx ^ tmp; }
    register int x0,x1,x2,x3,x4,x5;
    x1 = d[1];
    x2 = d[2];
    SWAP(x1, x2);
    x4 = d[4];
    x5 = d[5];
    SWAP(x4, x5);
    x0 = d[0];
    SWAP(x0, x2);
    x3 = d[3];
    SWAP(x3, x5);
    SWAP(x0, x1);
    SWAP(x3, x4);
    SWAP(x1, x4);
    SWAP(x0, x3);
    d[0] = x0;
    SWAP(x2, x5);
    d[5] = x5;
    SWAP(x1, x3);
    d[1] = x1;
    SWAP(x2, x4);
    d[4] = x4;
    SWAP(x2, x3);
    d[2] = x2;
    d[3] = x3;

#undef SWAP
#undef min
#undef max
}

static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile ("rdtsc; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");
    return x;
}

void ran_fill(int n, int *a) {
    static int seed = 76521;
    while (n--) *a++ = (seed = seed *1812433253 + 12345);
}

#define NTESTS 4096
int main() {
    int i;
    int d[6*NTESTS];
    ran_fill(6*NTESTS, d);

    unsigned long long cycles = rdtsc();
    for (i = 0; i < 6*NTESTS ; i+=6) {
        sort6_fast(d+i);
    }
    cycles = rdtsc() - cycles;
    printf("Time is %.2lf\n", (double)cycles/(double)NTESTS);

    for (i = 0; i < 6*NTESTS ; i+=6) {
        if (d[i+0] > d[i+1] || d[i+1] > d[i+2] || d[i+2] > d[i+3] || d[i+3] > d[i+4] || d[i+4] > d[i+5])
            printf("d%d : %d %d %d %d %d %d\n", i,
                    d[i+0], d[i+1], d[i+2],
                    d[i+3], d[i+4], d[i+5]);
    }
    return 0;
}

テストスイートを変更して、並べ替えごとのクロックも報告し、さらに多くのテストを実行するようにしました(cmp関数は整数オーバーフローも処理するように更新されました)。これは、いくつかの異なるアーキテクチャでの結果です。AMD CPUでテストを試みましたが、使用可能なX61100Tではrdtscの信頼性がありません。

Clarkdale (i5-650)
==================
Direct call to qsort library function      635.14   575.65   581.61   577.76   521.12
Naive implementation (insertion sort)      538.30   135.36   134.89   240.62   101.23
Insertion Sort (Daniel Stutzbach)          424.48   159.85   160.76   152.01   151.92
Insertion Sort Unrolled                    339.16   125.16   125.81   129.93   123.16
Rank Order                                 184.34   106.58   54.74    93.24    94.09
Rank Order with registers                  127.45   104.65   53.79    98.05    97.95
Sorting Networks (Daniel Stutzbach)        269.77   130.56   128.15   126.70   127.30
Sorting Networks (Paul R)                  551.64   103.20   64.57    73.68    73.51
Sorting Networks 12 with Fast Swap         321.74   61.61    63.90    67.92    67.76
Sorting Networks 12 reordered Swap         318.75   60.69    65.90    70.25    70.06
Reordered Sorting Network w/ fast swap     145.91   34.17    32.66    32.22    32.18

Kentsfield (Core 2 Quad)
========================
Direct call to qsort library function      870.01   736.39   723.39   725.48   721.85
Naive implementation (insertion sort)      503.67   174.09   182.13   284.41   191.10
Insertion Sort (Daniel Stutzbach)          345.32   152.84   157.67   151.23   150.96
Insertion Sort Unrolled                    316.20   133.03   129.86   118.96   105.06
Rank Order                                 164.37   138.32   46.29    99.87    99.81
Rank Order with registers                  115.44   116.02   44.04    116.04   116.03
Sorting Networks (Daniel Stutzbach)        230.35   114.31   119.15   110.51   111.45
Sorting Networks (Paul R)                  498.94   77.24    63.98    62.17    65.67
Sorting Networks 12 with Fast Swap         315.98   59.41    58.36    60.29    55.15
Sorting Networks 12 reordered Swap         307.67   55.78    51.48    51.67    50.74
Reordered Sorting Network w/ fast swap     149.68   31.46    30.91    31.54    31.58

Sandy Bridge (i7-2600k)
=======================
Direct call to qsort library function      559.97   451.88   464.84   491.35   458.11
Naive implementation (insertion sort)      341.15   160.26   160.45   154.40   106.54
Insertion Sort (Daniel Stutzbach)          284.17   136.74   132.69   123.85   121.77
Insertion Sort Unrolled                    239.40   110.49   114.81   110.79   117.30
Rank Order                                 114.24   76.42    45.31    36.96    36.73
Rank Order with registers                  105.09   32.31    48.54    32.51    33.29
Sorting Networks (Daniel Stutzbach)        210.56   115.68   116.69   107.05   124.08
Sorting Networks (Paul R)                  364.03   66.02    61.64    45.70    44.19
Sorting Networks 12 with Fast Swap         246.97   41.36    59.03    41.66    38.98
Sorting Networks 12 reordered Swap         235.39   38.84    47.36    38.61    37.29
Reordered Sorting Network w/ fast swap     115.58   27.23    27.75    27.25    26.54

Nehalem (Xeon E5640)
====================
Direct call to qsort library function      911.62   890.88   681.80   876.03   872.89
Naive implementation (insertion sort)      457.69   236.87   127.68   388.74   175.28
Insertion Sort (Daniel Stutzbach)          317.89   279.74   147.78   247.97   245.09
Insertion Sort Unrolled                    259.63   220.60   116.55   221.66   212.93
Rank Order                                 140.62   197.04   52.10    163.66   153.63
Rank Order with registers                  84.83    96.78    50.93    109.96   54.73
Sorting Networks (Daniel Stutzbach)        214.59   220.94   118.68   120.60   116.09
Sorting Networks (Paul R)                  459.17   163.76   56.40    61.83    58.69
Sorting Networks 12 with Fast Swap         284.58   95.01    50.66    53.19    55.47
Sorting Networks 12 reordered Swap         281.20   96.72    44.15    56.38    54.57
Reordered Sorting Network w/ fast swap     128.34   50.87    26.87    27.91    28.02
于 2011-08-24T15:29:18.580 に答える
15

テストコードはかなり悪いです。それは最初の配列をオーバーフローし(ここで人々はコンパイラの警告を読みませんか?)、printfは間違った要素を出力しています、それは正当な理由なしにrdtscに.byteを使用します、実行は1つだけです(!)、それをチェックするものは何もありません最終結果は実際には正しく(したがって、微妙に間違ったものに「最適化」するのは非常に簡単です)、含まれているテストは非常に初歩的であり(負の数はありませんか?)、コンパイラーが関数全体をデッドコードとして破棄するのを止めることはできません。

そうは言っても、bitonicネットワークソリューションを改善するのも非常に簡単です。min / max/SWAPのものを次のように変更するだけです

#define SWAP(x,y) { int tmp; asm("mov %0, %2 ; cmp %1, %0 ; cmovg %1, %0 ; cmovg %2, %1" : "=r" (d[x]), "=r" (d[y]), "=r" (tmp) : "0" (d[x]), "1" (d[y]) : "cc"); }

そしてそれは私にとって約65%速くなります(Debian gcc 4.4.5 with -O2、amd64、Core i7)。

于 2011-08-24T16:10:21.187 に答える
15

数日前にGoogleからこの質問に出くわしました。これは、6つの整数の固定長配列をすばやく並べ替える必要もあったためです。ただし、私の場合、整数は32ビットではなく8ビットであり、Cのみを使用するという厳密な要件はありません。誰かに役立つ可能性がある場合に備えて、とにかく調査結果を共有したいと思いました...

可能な限り、SSEを使用して比較およびスワップ操作をベクトル化する、アセンブリ内のネットワークソートのバリアントを実装しました。配列を完全にソートするには、6つの「パス」が必要です。新しいメカニズムを使用して、PCMPGTB(ベクトル化された比較)の結果をPSHUFB(ベクトル化されたスワップ)のシャッフルパラメーターに直接変換し、PADDB(ベクトル化された追加)と場合によってはPAND(ビット単位のAND)命令のみを使用しました。

このアプローチには、真にブランチレス関数を生成するという副作用もありました。ジャンプの指示は一切ありません。

この実装は、質問で現在最速のオプションとしてマークされている実装(「単純なスワップを使用したネットワーク12のソート」)よりも約38%高速であるようです。char比較を公平にするために、テスト中に配列要素を使用するようにその実装を変更しました。

このアプローチは、16要素までの任意の配列サイズに適用できることに注意してください。アレイが大きくなると、他の選択肢よりも相対速度が大きくなると思います。

コードは、SSSE3を搭載したx86_64プロセッサ用のMASMで記述されています。この関数は、「新しい」Windowsx64呼び出し規約を使用します。ここにあります...

PUBLIC simd_sort_6

.DATA

ALIGN 16

pass1_shuffle   OWORD   0F0E0D0C0B0A09080706040503010200h
pass1_add       OWORD   0F0E0D0C0B0A09080706050503020200h
pass2_shuffle   OWORD   0F0E0D0C0B0A09080706030405000102h
pass2_and       OWORD   00000000000000000000FE00FEFE00FEh
pass2_add       OWORD   0F0E0D0C0B0A09080706050405020102h
pass3_shuffle   OWORD   0F0E0D0C0B0A09080706020304050001h
pass3_and       OWORD   00000000000000000000FDFFFFFDFFFFh
pass3_add       OWORD   0F0E0D0C0B0A09080706050404050101h
pass4_shuffle   OWORD   0F0E0D0C0B0A09080706050100020403h
pass4_and       OWORD   0000000000000000000000FDFD00FDFDh
pass4_add       OWORD   0F0E0D0C0B0A09080706050403020403h
pass5_shuffle   OWORD   0F0E0D0C0B0A09080706050201040300h
pass5_and       OWORD 0000000000000000000000FEFEFEFE00h
pass5_add       OWORD   0F0E0D0C0B0A09080706050403040300h
pass6_shuffle   OWORD   0F0E0D0C0B0A09080706050402030100h
pass6_add       OWORD   0F0E0D0C0B0A09080706050403030100h

.CODE

simd_sort_6 PROC FRAME

    .endprolog

    ; pxor xmm4, xmm4
    ; pinsrd xmm4, dword ptr [rcx], 0
    ; pinsrb xmm4, byte ptr [rcx + 4], 4
    ; pinsrb xmm4, byte ptr [rcx + 5], 5
    ; The benchmarked 38% faster mentioned in the text was with the above slower sequence that tied up the shuffle port longer.  Same on extract
    ; avoiding pins/extrb also means we don't need SSE 4.1, but SSSE3 CPUs without SSE4.1 (e.g. Conroe/Merom) have slow pshufb.
    movd    xmm4, dword ptr [rcx]
    pinsrw  xmm4,  word ptr [rcx + 4], 2  ; word 2 = bytes 4 and 5


    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass1_shuffle]
    pcmpgtb xmm5, xmm4
    paddb xmm5, oword ptr [pass1_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass2_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass2_and]
    paddb xmm5, oword ptr [pass2_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass3_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass3_and]
    paddb xmm5, oword ptr [pass3_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass4_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass4_and]
    paddb xmm5, oword ptr [pass4_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass5_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass5_and]
    paddb xmm5, oword ptr [pass5_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass6_shuffle]
    pcmpgtb xmm5, xmm4
    paddb xmm5, oword ptr [pass6_add]
    pshufb xmm4, xmm5

    ;pextrd dword ptr [rcx], xmm4, 0    ; benchmarked with this
    ;pextrb byte ptr [rcx + 4], xmm4, 4 ; slower version
    ;pextrb byte ptr [rcx + 5], xmm4, 5
    movd   dword ptr [rcx], xmm4
    pextrw  word ptr [rcx + 4], xmm4, 2  ; x86 is little-endian, so this is the right order

    ret

simd_sort_6 ENDP

END

これを実行可能オブジェクトにコンパイルして、Cプロジェクトにリンクすることができます。Visual Studioでこれを行う方法については、この記事を参照してください。次のCプロトタイプを使用して、Cコードから関数を呼び出すことができます。

void simd_sort_6(char *values);
于 2012-12-02T03:18:17.007 に答える
13

私は提供されたスワップマクロが本当に好きですが:

#define min(x, y) (y ^ ((x ^ y) & -(x < y)))
#define max(x, y) (x ^ ((x ^ y) & -(x < y)))
#define SWAP(x,y) { int tmp = min(d[x], d[y]); d[y] = max(d[x], d[y]); d[x] = tmp; }

私は改善が見られます(これは優れたコンパイラーが行う可能性があります):

#define SWAP(x,y) { int tmp = ((x ^ y) & -(y < x)); y ^= tmp; x ^= tmp; }

minとmaxがどのように機能するかに注目し、共通部分式を明示的にプルします。これにより、最小マクロと最大マクロが完全に削除されます。

于 2011-08-24T14:18:42.217 に答える
12

ベンチマークを実行し、実際のコンパイラで生成されたアセンブリを確認せずに、最小/最大を最適化しないでください。条件付き移動命令を使用してGCCに最小値を最適化させると、33%のスピードアップが得られます。

#define SWAP(x,y) { int dx = d[x], dy = d[y], tmp; tmp = d[x] = dx < dy ? dx : dy; d[y] ^= dx ^ tmp; }

(テストコードでは280サイクルと420サイクル)。?:でmaxを実行することはほぼ同じであり、ノイズでほとんど失われますが、上記は少し高速です。このSWAPは、GCCとClangの両方で高速です。

コンパイラはまた、レジスタ割り当てとエイリアス分析で例外的な仕事をしており、d [x]をローカル変数に効果的に前もって移動し、最後にメモリにコピーして戻すだけです。実際、ローカル変数(など)を完全に操作する場合よりも優れていますd0 = d[0], d1 = d[1], d2 = d[2], d3 = d[3], d4 = d[4], d5 = d[5]。これを書いているのは、強力な最適化を想定しているにもかかわらず、最小/最大でコンパイラーを凌駕しようとしているためです。:)

ちなみに、ClangとGCCを試してみました。それらは同じ最適化を行いますが、スケジュールの違いにより、2つは結果にいくらかの変動があり、どちらが速いか遅いかを実際に言うことはできません。GCCはソーティングネットワークで高速であり、Clangは2次ソートで高速です。

完全を期すために、展開されたバブルソートと挿入ソートも可能です。これがバブルソートです:

SWAP(0,1); SWAP(1,2); SWAP(2,3); SWAP(3,4); SWAP(4,5);
SWAP(0,1); SWAP(1,2); SWAP(2,3); SWAP(3,4);
SWAP(0,1); SWAP(1,2); SWAP(2,3);
SWAP(0,1); SWAP(1,2);
SWAP(0,1);

挿入ソートは次のとおりです。

//#define ITER(x) { if (t < d[x]) { d[x+1] = d[x]; d[x] = t; } }
//Faster on x86, probably slower on ARM or similar:
#define ITER(x) { d[x+1] ^= t < d[x] ? d[x] ^ d[x+1] : 0; d[x] = t < d[x] ? t : d[x]; }
static inline void sort6_insertion_sort_unrolled_v2(int * d){
    int t;
    t = d[1]; ITER(0);
    t = d[2]; ITER(1); ITER(0);
    t = d[3]; ITER(2); ITER(1); ITER(0);
    t = d[4]; ITER(3); ITER(2); ITER(1); ITER(0);
    t = d[5]; ITER(4); ITER(3); ITER(2); ITER(1); ITER(0);

この挿入ソートはDanielStutzbachのものよりも高速であり、ITERは3つの命令(SWAPの場合は4)でしか実行できないため、GPUまたは予測のあるコンピューターで特に適しています。たとえばt = d[2]; ITER(1); ITER(0);、ARMアセンブリの行は次のとおりです。

    MOV    r6, r2
    CMP    r6, r1
    MOVLT  r2, r1
    MOVLT  r1, r6
    CMP    r6, r0
    MOVLT  r1, r0
    MOVLT  r0, r6

6つの要素の場合、挿入ソートはソーティングネットワークと競合します(12スワップ対15反復は、4命令/スワップ対3命令/反復のバランスを取ります)。もちろんバブルソートは遅いです。ただし、挿入ソートはO(n ^ 2)であり、ソートネットワークはO(n log n)であるため、サイズが大きくなると、それは当てはまりません。

于 2011-08-24T20:40:42.127 に答える
11

テストスイートを識別できないPPCアーキテクチャマシンに移植しました(コードに触れる必要はありませんでした。テストの反復を増やし、8つのテストケースを使用して結果をmodで汚染しないようにし、x86固有のrdtscを置き換えます)。

qsortライブラリ関数への直接呼び出し:101

ナイーブな実装(挿入ソート):299

挿入ソート(Daniel Stutzbach) :108

挿入ソート展開 :51

ソーティングネットワーク(Daniel Stutzbach) :26

ソーティングネットワーク(Paul R) :85

高速スワップを使用したネットワーク12の並べ替え :117

ソーティングネットワーク12の並べ替えスワップ :116

ランク順 :56

于 2011-08-24T13:15:06.827 に答える
7

XORスワップは、スワッピング関数で役立つ場合があります。

void xorSwap (int *x, int *y) {
     if (*x != *y) {
         *x ^= *y;
         *y ^= *x;
         *x ^= *y;
     }
 }

ifを使用すると、コードに大きな相違が生じる可能性がありますが、すべてのintが一意であることが保証されている場合は、これが便利です。

于 2011-08-24T11:36:20.647 に答える
5

これを試して、これらの例から学ぶことを楽しみにしていますが、最初に1.5 GHz PPC Powerbook G4(1 GB DDR RAM付き)からいくつかのタイミングを学びます。(タイミングについては、 http: //www.mcs.anl.gov/~kazutomo/rdtsc.htmlからPPC用の同様のrdtscのようなタイマーを借りました。)プログラムを数回実行すると、絶対的な結果は変化しましたが、一貫して最速のテストは「InsertionSort(Daniel Stutzbach)」で、「InsertionSortUnrolled」がすぐ近くにありました。

これが最後の時間のセットです:

**Direct call to qsort library function** : 164
**Naive implementation (insertion sort)** : 138
**Insertion Sort (Daniel Stutzbach)**     : 85
**Insertion Sort Unrolled**               : 97
**Sorting Networks (Daniel Stutzbach)**   : 457
**Sorting Networks (Paul R)**             : 179
**Sorting Networks 12 with Fast Swap**    : 238
**Sorting Networks 12 reordered Swap**    : 236
**Rank Order**                            : 116
于 2011-08-25T02:49:06.720 に答える
4

このスレッドへの私の貢献は次のとおりです。一意の値を含む6メンバーのintベクトル(valp)用に最適化された1、4ギャップのシェルソート。

void shellsort (int *valp)
{      
  int c,a,*cp,*ip=valp,*ep=valp+5;

  c=*valp;    a=*(valp+4);if (c>a) {*valp=    a;*(valp+4)=c;}
  c=*(valp+1);a=*(valp+5);if (c>a) {*(valp+1)=a;*(valp+5)=c;}

  cp=ip;    
  do
  {
    c=*cp;
    a=*(cp+1);
    do
    {
      if (c<a) break;

      *cp=a;
      *(cp+1)=c;
      cp-=1;
      c=*cp;
    } while (cp>=valp);
    ip+=1;
    cp=ip;
  } while (ip<ep);
}

デュアルコアAthlonM300@ 2 Ghz(DDR2メモリ)を搭載したHP dv7-3010soラップトップでは、165クロックサイクルで実行されます。これは、すべての一意のシーケンスのタイミングから計算された平均です(合計で6!/ 720)。OpenWatcom1.8を使用してWin32にコンパイルされます。ループは本質的に挿入ソートであり、16命令/37バイト長です。

コンパイルする64ビット環境がありません。

于 2012-03-14T11:14:14.800 に答える
3

ここで挿入ソートがかなり競争力がある場合は、シェルソートを試すことをお勧めします。6つの要素はおそらく少なすぎて最高のものにはならないのではないかと思いますが、試してみる価値があるかもしれません。

サンプルコード、テストされていない、デバッグされていないなど。最適なものを見つけるために、inc=4およびinc-=3シーケンスを調整します(たとえば、inc = 2、inc-= 1を試してください)。

static __inline__ int sort6(int * d) {
    char j, i;
    int tmp;
    for (inc = 4; inc > 0; inc -= 3) {
        for (i = inc; i < 5; i++) {
            tmp = a[i];
            j = i;
            while (j >= inc && a[j - inc] > tmp) {
                a[j] = a[j - inc];
                j -= inc;
            }
            a[j] = tmp;
        }
    }
}

これが勝つとは思いませんが、誰かが10個の要素の並べ替えについて質問を投稿した場合、誰が知っていますか...

ウィキペディアによると、これはソーティングネットワークと組み合わせることができます: Pratt、V(1979)。シェルソートとソーティングネットワーク(コンピューターサイエンスの優れた論文)。花輪。ISBN 0-824-04406-1

于 2011-08-24T14:37:49.483 に答える
3

私は超遅刻だと知っていますが、いくつかの異なる解決策を試すことに興味がありました。まず、そのペーストをクリーンアップしてコンパイルし、リポジトリに配置しました。他の人がそれを試さないように、私はいくつかの望ましくない解決策を行き止まりとして保持しました。この中には、x1>x2が1回計算されるようにする最初のソリューションがありました。最適化後は、他の単純なバージョンよりも高速ではありません。

ランク順並べ替えのループバージョンを追加しました。この調査の私自身のアプリケーションは2〜8項目の並べ替えであり、引数の数は可変であるため、ループが必要です。これが、私がソーティングネットワークソリューションを無視した理由でもあります。

テストコードは重複が正しく処理されたことをテストしなかったため、既存のソリューションはすべて正しいものの、重複が正しく処理されたことを確認するためにテストコードに特別なケースを追加しました。

次に、完全にAVXレジスタにある挿入ソートを作成しました。私のマシンでは、他の挿入ソートより25%高速ですが、ランク順より100%低速です。私はこれを純粋に実験のために行いましたが、挿入ソートでの分岐のためにこれが改善されるとは思っていませんでした。

static inline void sort6_insertion_sort_avx(int* d) {
    __m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], 0, 0);
    __m256i index = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
    __m256i shlpermute = _mm256_setr_epi32(7, 0, 1, 2, 3, 4, 5, 6);
    __m256i sorted = _mm256_setr_epi32(d[0], INT_MAX, INT_MAX, INT_MAX,
            INT_MAX, INT_MAX, INT_MAX, INT_MAX);
    __m256i val, gt, permute;
    unsigned j;
     // 8 / 32 = 2^-2
#define ITER(I) \
        val = _mm256_permutevar8x32_epi32(src, _mm256_set1_epi32(I));\
        gt =  _mm256_cmpgt_epi32(sorted, val);\
        permute =  _mm256_blendv_epi8(index, shlpermute, gt);\
        j = ffs( _mm256_movemask_epi8(gt)) >> 2;\
        sorted = _mm256_blendv_epi8(_mm256_permutevar8x32_epi32(sorted, permute),\
                val, _mm256_cmpeq_epi32(index, _mm256_set1_epi32(j)))
    ITER(1);
    ITER(2);
    ITER(3);
    ITER(4);
    ITER(5);
    int x[8];
    _mm256_storeu_si256((__m256i*)x, sorted);
    d[0] = x[0]; d[1] = x[1]; d[2] = x[2]; d[3] = x[3]; d[4] = x[4]; d[5] = x[5];
#undef ITER
}

次に、AVXを使用してランク順の並べ替えを作成しました。これは他のランク順ソリューションの速度と一致しますが、高速ではありません。ここでの問題は、AVXでしかインデックスを計算できないことです。次に、インデックスのテーブルを作成する必要があります。これは、計算がソースベースではなく宛先ベースであるためです。ソースベースのインデックスから宛先ベースのインデックスへの変換を参照してください。

static inline void sort6_rank_order_avx(int* d) {
    __m256i ror = _mm256_setr_epi32(5, 0, 1, 2, 3, 4, 6, 7);
    __m256i one = _mm256_set1_epi32(1);
    __m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], INT_MAX, INT_MAX);
    __m256i rot = src;
    __m256i index = _mm256_setzero_si256();
    __m256i gt, permute;
    __m256i shl = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 6, 6);
    __m256i dstIx = _mm256_setr_epi32(0,1,2,3,4,5,6,7);
    __m256i srcIx = dstIx;
    __m256i eq = one;
    __m256i rotIx = _mm256_setzero_si256();
#define INC(I)\
    rot = _mm256_permutevar8x32_epi32(rot, ror);\
    gt = _mm256_cmpgt_epi32(src, rot);\
    index = _mm256_add_epi32(index, _mm256_and_si256(gt, one));\
    index = _mm256_add_epi32(index, _mm256_and_si256(eq,\
                _mm256_cmpeq_epi32(src, rot)));\
    eq = _mm256_insert_epi32(eq, 0, I)
    INC(0);
    INC(1);
    INC(2);
    INC(3);
    INC(4);
    int e[6];
    e[0] = d[0]; e[1] = d[1]; e[2] = d[2]; e[3] = d[3]; e[4] = d[4]; e[5] = d[5];
    int i[8];
    _mm256_storeu_si256((__m256i*)i, index);
    d[i[0]] = e[0]; d[i[1]] = e[1]; d[i[2]] = e[2]; d[i[3]] = e[3]; d[i[4]] = e[4]; d[i[5]] = e[5];
}

リポジトリはここにあります:https ://github.com/eyepatchParrot/sort6/

于 2016-09-05T19:59:56.253 に答える
2

この質問はかなり古くなりつつありますが、私は実際に最近同じ問題を解決する必要がありました。小さな配列をソートするための高速な苦痛です。私の知識を共有するのは良い考えだと思いました。最初はソーティングネットワークを使用することから始めましたが、最終的に、6つの値のすべての順列をソートするために実行される比較の総数が、ソーティングネットワークよりも少なく、挿入ソートよりも少ない他のアルゴリズムを見つけることができました。スワップの数は数えませんでした。私はそれがほぼ同等であると期待します(多分少し高いかもしれません)。

アルゴリズムsort6は、アルゴリズムsort4を使用するアルゴリズムを使用しますsort3。これは、いくつかの軽いC ++形式での実装です(元の実装はテンプレートが多いため、任意のランダムアクセスイテレータおよび任意の適切な比較関数で動作できます)。

3つの値の並べ替え

次のアルゴリズムは、展開された挿入ソートです。2つのスワップ(6つの割り当て)を実行する必要がある場合、代わりに4つの割り当てを使用します。

void sort3(int* array)
{
    if (array[1] < array[0]) {
        if (array[2] < array[0]) {
            if (array[2] < array[1]) {
                std::swap(array[0], array[2]);
            } else {
                int tmp = array[0];
                array[0] = array[1];
                array[1] = array[2];
                array[2] = tmp;
            }
        } else {
            std::swap(array[0], array[1]);
        }
    } else {
        if (array[2] < array[1]) {
            if (array[2] < array[0]) {
                int tmp = array[2];
                array[2] = array[1];
                array[1] = array[0];
                array[0] = tmp;
            } else {
                std::swap(array[1], array[2]);
            }
        }
    }
}

並べ替えには、配列の可能な順列ごとに多かれ少なかれ1つのブランチがあり、3つの値を並べ替えるために2〜3の比較と最大4つの割り当てを使用するため、少し複雑に見えます。

4つの値を並べ替える

次に、これを呼び出しsort3て、配列の最後の要素を使用して展開された挿入ソートを実行します。

void sort4(int* array)
{
    // Sort the first 3 elements
    sort3(array);

    // Insert the 4th element with insertion sort 
    if (array[3] < array[2]) {
        std::swap(array[2], array[3]);
        if (array[2] < array[1]) {
            std::swap(array[1], array[2]);
            if (array[1] < array[0]) {
                std::swap(array[0], array[1]);
            }
        }
    }
}

このアルゴリズムは、3〜6回の比較と、最大5回のスワップを実行します。挿入ソートを展開するのは簡単ですが、最後のソートには別のアルゴリズムを使用します...

6つの値の並べ替え

これは、私が二重挿入ソートと呼んだものの展開バージョンを使用しています。名前はそれほど素晴らしいものではありませんが、非常にわかりやすく、次のように機能します。

  • 配列の最初と最後の要素以外のすべてを並べ替えます。
  • 最初の要素が最後の要素より大きい場合は、最初の要素と配列の要素を入れ替えます。
  • 最初の要素を前からソートされたシーケンスに挿入し、次に最後の要素を後ろから挿入します。

スワップ後、最初の要素は常に最後の要素よりも小さくなります。つまり、並べ替えられたシーケンスにそれらを挿入する場合、最悪の場合、2つの要素を挿入するための比較はN回を超えることはありません。最初の要素は3番目の位置に挿入されており、最後の要素は4番目の位置より下に挿入できません。

void sort6(int* array)
{
    // Sort everything but first and last elements
    sort4(array+1);

    // Switch first and last elements if needed
    if (array[5] < array[0]) {
        std::swap(array[0], array[5]);
    }

    // Insert first element from the front
    if (array[1] < array[0]) {
        std::swap(array[0], array[1]);
        if (array[2] < array[1]) {
            std::swap(array[1], array[2]);
            if (array[3] < array[2]) {
                std::swap(array[2], array[3]);
                if (array[4] < array[3]) {
                    std::swap(array[3], array[4]);
                }
            }
        }
    }

    // Insert last element from the back
    if (array[5] < array[4]) {
        std::swap(array[4], array[5]);
        if (array[4] < array[3]) {
            std::swap(array[3], array[4]);
            if (array[3] < array[2]) {
                std::swap(array[2], array[3]);
                if (array[2] < array[1]) {
                    std::swap(array[1], array[2]);
                }
            }
        }
    }
}

6つの値のすべての順列に対する私のテストは、このアルゴリズムが常に6から13の比較を実行することを示しています。実行されたスワップの数は計算しませんでしたが、最悪の場合、11より多くなるとは思いません。

この質問が実際の問題を表していない場合でも、これが役立つことを願っています:)

編集:提供されたベンチマークに入れた後、それは興味深い選択肢のほとんどよりも明らかに遅いです。展開された挿入ソートよりもパフォーマンスが少し向上する傾向がありますが、それだけです。基本的に、これは整数に最適なソートではありませんが、コストのかかる比較操作を行うタイプには興味深い可能性があります。

于 2015-10-07T09:57:51.520 に答える
2

少なくとも私のシステムでは、以下で定義されている関数sort6_iterator()sort6_iterator_local()両方が、上記の現在の記録保持者と少なくとも同じくらい速く、しばしば著しく速く実行されていることがわかりました。

#define MIN(x, y) (x<y?x:y)
#define MAX(x, y) (x<y?y:x)

template<class IterType> 
inline void sort6_iterator(IterType it) 
{
#define SWAP(x,y) { const auto a = MIN(*(it + x), *(it + y)); \
  const auto b = MAX(*(it + x), *(it + y)); \
  *(it + x) = a; *(it + y) = b; }

  SWAP(1, 2) SWAP(4, 5)
  SWAP(0, 2) SWAP(3, 5)
  SWAP(0, 1) SWAP(3, 4)
  SWAP(1, 4) SWAP(0, 3)
  SWAP(2, 5) SWAP(1, 3)
  SWAP(2, 4)
  SWAP(2, 3)
#undef SWAP
}

std::vectorこの関数をタイミングコードでイテレータに渡しました。

私は(このようなコメントや他の場所から)イテレータを使用すると、イテレータが参照するメモリに何が起こり、何が起こり得ないかについて特定の保証が得られると思います。ソートコードをより適切に最適化します(たとえば、ポインターを使用すると、コンパイラーはすべてのポインターが異なるメモリ位置を指していることを確認できません)。私の記憶が正しければ、これは、などの多くのSTLアルゴリズムが一般的に非常に優れたパフォーマンスを発揮する理由の一部でもあります。std::sort()

さらに、 (関数が呼び出されるsort6_iterator()コンテキストによっては)次の並べ替え関数によって一貫してパフォーマンスが向上する場合があります。この並べ替え関数は、データを並べ替える前にローカル変数にコピーします。1定義されているローカル変数は6つしかないため、これらのローカル変数がプリミティブの場合、実際にはRAMに格納されることはなく、関数呼び出しが終了するまでCPUのレジスタにのみ格納されるため、この並べ替えに役立ちます。高速に機能します。(これは、コンパイラーが、別個のローカル変数がメモリー内に別個の位置を持っていることを知るのにも役立ちます)。

template<class IterType> 
inline void sort6_iterator_local(IterType it) 
{
#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
  const auto b = MAX(data##x, data##y); \
  data##x = a; data##y = b; }
//DD = Define Data
#define DD1(a)   auto data##a = *(it + a);
#define DD2(a,b) auto data##a = *(it + a), data##b = *(it + b);
//CB = Copy Back
#define CB(a) *(it + a) = data##a;

  DD2(1,2)    SWAP(1, 2)
  DD2(4,5)    SWAP(4, 5)
  DD1(0)      SWAP(0, 2)
  DD1(3)      SWAP(3, 5)
  SWAP(0, 1)  SWAP(3, 4)
  SWAP(1, 4)  SWAP(0, 3)   CB(0)
  SWAP(2, 5)  CB(5)
  SWAP(1, 3)  CB(1)
  SWAP(2, 4)  CB(4)
  SWAP(2, 3)  CB(2)        CB(3)
#undef CB
#undef DD2
#undef DD1
#undef SWAP
}

SWAP()次のように定義すると、パフォーマンスがわずかに向上する場合がありますが、ほとんどの場合、パフォーマンスがわずかに低下するか、パフォーマンスの違いはごくわずかです。

#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
  data##y = MAX(data##x, data##y); \
  data##x = a; }

プリミティブデータ型でソートアルゴリズムが必要な場合は、gcc -O3は、ソート関数の呼び出しが1に表示されるコンテキストに関係なく、一貫して最適化に優れています。入力の受け渡し方法に応じて、次の2つのいずれかを試してください。アルゴリズム:

template<class T> inline void sort6(T it) {
#define SORT2(x,y) {if(data##x>data##y){auto a=std::move(data##y);data##y=std::move(data##x);data##x=std::move(a);}}
#define DD1(a)   register auto data##a=*(it+a);
#define DD2(a,b) register auto data##a=*(it+a);register auto data##b=*(it+b);
#define CB1(a)   *(it+a)=data##a;
#define CB2(a,b) *(it+a)=data##a;*(it+b)=data##b;
  DD2(1,2) SORT2(1,2)
  DD2(4,5) SORT2(4,5)
  DD1(0)   SORT2(0,2)
  DD1(3)   SORT2(3,5)
  SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
  SORT2(1,4) SORT2(0,3) CB1(0)
  SORT2(2,4) CB1(4)
  SORT2(1,3) CB1(1)
  SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

または、参照によって変数を渡したい場合は、これを使用します(以下の関数は最初の5行で上記とは異なります)。

template<class T> inline void sort6(T& e0, T& e1, T& e2, T& e3, T& e4, T& e5) {
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a)   register auto data##a=e##a;
#define DD2(a,b) register auto data##a=e##a;register auto data##b=e##b;
#define CB1(a)   e##a=data##a;
#define CB2(a,b) e##a=data##a;e##b=data##b;
  DD2(1,2) SORT2(1,2)
  DD2(4,5) SORT2(4,5)
  DD1(0)   SORT2(0,2)
  DD1(3)   SORT2(3,5)
  SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
  SORT2(1,4) SORT2(0,3) CB1(0)
  SORT2(2,4) CB1(4)
  SORT2(1,3) CB1(1)
  SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

キーワードを使用する理由registerは、これがレジスタにこれらの値が必要であることがわかっている数少ない回数の1つだからです。がないregisterと、コンパイラはほとんどの場合これを理解しますが、そうでない場合もあります。キーワードを使用すると、registerこの問題を解決するのに役立ちます。ただし、通常はキーワードを使用しないでregisterください。コードを高速化するよりも低速化する可能性が高いためです。

また、テンプレートの使用にも注意してください。inlineキーワードを使用しても、テンプレート関数は一般にバニラC関数よりもgccによってはるかに積極的に最適化されるため、これは意図的に行われます(これは、バニラC関数の関数ポインターを処理する必要があるgccと関係がありますが、テンプレート関数とは関係ありません)。

  1. さまざまな並べ替え関数のタイミングを調整しているときに、並べ替え関数の呼び出しが行われたコンテキスト(つまり、周囲のコード)がパフォーマンスに大きな影響を与えていることに気付きました。これは、関数がインライン化されてから最適化されたことが原因である可能性があります。たとえば、プログラムが十分に単純である場合、通常、ソート関数にポインターを渡すこととイテレーターを渡すことのパフォーマンスに大きな違いはありませんでした。それ以外の場合、イテレータを使用すると、通常、パフォーマンスが著しく向上し、(少なくともこれまでの私の経験では)パフォーマンスが著しく低下することはありませんでした。これは、g++が十分に単純なコードをグローバルに最適化できるためだと思います。
于 2017-06-03T03:45:39.850 に答える
1

あなたの質問には2つの部分があると思います。

  • 1つ目は、最適なアルゴリズムを決定することです。これは、少なくともこの場合は、可能なすべての順序(それほど多くはありません)をループすることによって実行されます。これにより、比較とスワップの正確な最小、最大、平均、および標準偏差を計算できます。準優勝者も2人も手元に置いてください。
  • 2つ目は、アルゴリズムを最適化することです。教科書のコード例を、実際のアルゴリズムを意味し、無駄のないものに変換するために、多くのことができます。アルゴリズムを必要な範囲で最適化できないことに気付いた場合は、次点者を試してください。

パイプラインを空にすることについてはあまり心配しません(現在のx86を想定):分岐予測は長い道のりを歩んできました。私が心配するのは、コードとデータがそれぞれ1つのキャッシュ行(コードの場合は2つ)に収まるようにすることです。フェッチの待ち時間が短くなると、ストールを補うことができます。また、内部ループはおそらく10命令程度になることを意味します。これは、本来あるべき場所にあります(私のソートアルゴリズムには、それぞれ10命令/ 22バイトと9/22の長さの2つの異なる内部ループがあります)。コードにdivが含まれていないと仮定すると、目がくらむほど高速になると確信できます。

于 2012-03-06T10:33:07.123 に答える
1

私はこれが古い質問であることを知っています。

しかし、私は共有したい別の種類のソリューションを書いたばかりです。
ネストされたMINMAXのみを使用して、

それぞれ114個使用するので速くはありませんが、
とても簡単に75個に減らすことができます-> Pastebin

しかし、それはもはや純粋に最小最大ではありません。

うまくいくかもしれないのは、AVXで一度に複数の整数に対して最小/最大を実行することです

PMINSWリファレンス

#include <stdio.h>

static __inline__ int MIN(int a, int b){
int result =a;
__asm__ ("pminsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ int MAX(int a, int b){
int result = a;
__asm__ ("pmaxsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ unsigned long long rdtsc(void){
  unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" :
  "=A" (x));
  return x;
}

#define MIN3(a, b, c) (MIN(MIN(a,b),c))
#define MIN4(a, b, c, d) (MIN(MIN(a,b),MIN(c,d)))

static __inline__ void sort6(int * in) {
  const int A=in[0], B=in[1], C=in[2], D=in[3], E=in[4], F=in[5];

  in[0] = MIN( MIN4(A,B,C,D),MIN(E,F) );

  const int
  AB = MAX(A, B),
  AC = MAX(A, C),
  AD = MAX(A, D),
  AE = MAX(A, E),
  AF = MAX(A, F),
  BC = MAX(B, C),
  BD = MAX(B, D),
  BE = MAX(B, E),
  BF = MAX(B, F),
  CD = MAX(C, D),
  CE = MAX(C, E),
  CF = MAX(C, F),
  DE = MAX(D, E),
  DF = MAX(D, F),
  EF = MAX(E, F);

  in[1] = MIN4 (
  MIN4( AB, AC, AD, AE ),
  MIN4( AF, BC, BD, BE ),
  MIN4( BF, CD, CE, CF ),
  MIN3( DE, DF, EF)
  );

  const int
  ABC = MAX(AB,C),
  ABD = MAX(AB,D),
  ABE = MAX(AB,E),
  ABF = MAX(AB,F),
  ACD = MAX(AC,D),
  ACE = MAX(AC,E),
  ACF = MAX(AC,F),
  ADE = MAX(AD,E),
  ADF = MAX(AD,F),
  AEF = MAX(AE,F),
  BCD = MAX(BC,D),
  BCE = MAX(BC,E),
  BCF = MAX(BC,F),
  BDE = MAX(BD,E),
  BDF = MAX(BD,F),
  BEF = MAX(BE,F),
  CDE = MAX(CD,E),
  CDF = MAX(CD,F),
  CEF = MAX(CE,F),
  DEF = MAX(DE,F);

  in[2] = MIN( MIN4 (
  MIN4( ABC, ABD, ABE, ABF ),
  MIN4( ACD, ACE, ACF, ADE ),
  MIN4( ADF, AEF, BCD, BCE ),
  MIN4( BCF, BDE, BDF, BEF )),
  MIN4( CDE, CDF, CEF, DEF )
  );


  const int
  ABCD = MAX(ABC,D),
  ABCE = MAX(ABC,E),
  ABCF = MAX(ABC,F),
  ABDE = MAX(ABD,E),
  ABDF = MAX(ABD,F),
  ABEF = MAX(ABE,F),
  ACDE = MAX(ACD,E),
  ACDF = MAX(ACD,F),
  ACEF = MAX(ACE,F),
  ADEF = MAX(ADE,F),
  BCDE = MAX(BCD,E),
  BCDF = MAX(BCD,F),
  BCEF = MAX(BCE,F),
  BDEF = MAX(BDE,F),
  CDEF = MAX(CDE,F);

  in[3] = MIN4 (
  MIN4( ABCD, ABCE, ABCF, ABDE ),
  MIN4( ABDF, ABEF, ACDE, ACDF ),
  MIN4( ACEF, ADEF, BCDE, BCDF ),
  MIN3( BCEF, BDEF, CDEF )
  );

  const int
  ABCDE= MAX(ABCD,E),
  ABCDF= MAX(ABCD,F),
  ABCEF= MAX(ABCE,F),
  ABDEF= MAX(ABDE,F),
  ACDEF= MAX(ACDE,F),
  BCDEF= MAX(BCDE,F);

  in[4]= MIN (
  MIN4( ABCDE, ABCDF, ABCEF, ABDEF ),
  MIN ( ACDEF, BCDEF )
  );

  in[5] = MAX(ABCDE,F);
}

int main(int argc, char ** argv) {
  int d[6][6] = {
    {1, 2, 3, 4, 5, 6},
    {6, 5, 4, 3, 2, 1},
    {100, 2, 300, 4, 500, 6},
    {100, 2, 3, 4, 500, 6},
    {1, 200, 3, 4, 5, 600},
    {1, 1, 2, 1, 2, 1}
  };

  unsigned long long cycles = rdtsc();
  for (int i = 0; i < 6; i++) {
    sort6(d[i]);
  }
  cycles = rdtsc() - cycles;
  printf("Time is %d\n", (unsigned)cycles);

  for (int i = 0; i < 6; i++) {
    printf("d%d : %d %d %d %d %d %d\n", i,
     d[i][0], d[i][1], d[i][2],
     d[i][3], d[i][4], d[i][5]);
  }
}

編集:
レックスカーに触発されたランク順ソリューション、上記の混乱よりもはるかに高速

static void sort6(int *o) {
const int 
A=o[0],B=o[1],C=o[2],D=o[3],E=o[4],F=o[5];
const unsigned char
AB = A>B, AC = A>C, AD = A>D, AE = A>E,
          BC = B>C, BD = B>D, BE = B>E,
                    CD = C>D, CE = C>E,
                              DE = D>E,
a =          AB + AC + AD + AE + (A>F),
b = 1 - AB      + BC + BD + BE + (B>F),
c = 2 - AC - BC      + CD + CE + (C>F),
d = 3 - AD - BD - CD      + DE + (D>F),
e = 4 - AE - BE - CE - DE      + (E>F);
o[a]=A; o[b]=B; o[c]=C; o[d]=D; o[e]=E;
o[15-a-b-c-d-e]=F;
}
于 2017-09-28T19:56:12.233 に答える
1

展開されたFord-Johnsonマージ挿入ソートを試してみようと思いました。これにより、可能な限り最小の比較数(ceil(log2(6!))= 10)が達成され、スワップは行われません。ただし、競合はしません(最悪のソーティングネットワークソリューションよりもわずかに良いタイミングが得られましたsort6_sorting_network_v1)。

値を6つのレジスタにロードし、8〜10の比較を実行して、720=6のどれかを決定します。ケースに入れてから、それらの720オーダーの適切な1つにレジスタを書き戻します(ケースごとに個別のコード)。最終的な書き戻しまで、スワップや並べ替えはありません。生成されたアセンブリコードを見ていません。

static inline void sort6_ford_johnson_unrolled(int *D) {
  register int a = D[0], b = D[1], c = D[2], d = D[3], e = D[4], f = D[5];
  #define abcdef(a,b,c,d,e,f) (D[0]=a, D[1]=b, D[2]=c, D[3]=d, D[4]=e, D[5]=f)
  #define abdef_cd(a,b,c,d,e,f) (c<a ? abcdef(c,a,b,d,e,f) \
                                     : c<b ? abcdef(a,c,b,d,e,f) \
                                           : abcdef(a,b,c,d,e,f))
  #define abedf_cd(a,b,c,d,e,f) (c<b ? c<a ? abcdef(c,a,b,e,d,f) \
                                           : abcdef(a,c,b,e,d,f) \
                                     : c<e ? abcdef(a,b,c,e,d,f) \
                                           : abcdef(a,b,e,c,d,f))
  #define abdf_cd_ef(a,b,c,d,e,f) (e<b ? e<a ? abedf_cd(e,a,c,d,b,f) \
                                             : abedf_cd(a,e,c,d,b,f) \
                                       : e<d ? abedf_cd(a,b,c,d,e,f) \
                                             : abdef_cd(a,b,c,d,e,f))
  #define abd_cd_ef(a,b,c,d,e,f) (d<f ? abdf_cd_ef(a,b,c,d,e,f) \
                                      : b<f ? abdf_cd_ef(a,b,e,f,c,d) \
                                            : abdf_cd_ef(e,f,a,b,c,d))
  #define ab_cd_ef(a,b,c,d,e,f) (b<d ? abd_cd_ef(a,b,c,d,e,f) \
                                     : abd_cd_ef(c,d,a,b,e,f))
  #define ab_cd(a,b,c,d,e,f) (e<f ? ab_cd_ef(a,b,c,d,e,f) \
                                  : ab_cd_ef(a,b,c,d,f,e))
  #define ab(a,b,c,d,e,f) (c<d ? ab_cd(a,b,c,d,e,f) \
                               : ab_cd(a,b,d,c,e,f))
  a<b ? ab(a,b,c,d,e,f)
      : ab(b,a,c,d,e,f);
  #undef ab
  #undef ab_cd
  #undef ab_cd_ef
  #undef abd_cd_ef
  #undef abdf_cd_ef
  #undef abedf_cd
  #undef abdef_cd
  #undef abcdef
}

TEST(ford_johnson_unrolled,   "Unrolled Ford-Johnson Merge-Insertion sort");
于 2021-11-23T17:52:50.323 に答える
0

「ソートされたリストのマージ」ソートを試してください。:)2つの配列を使用します。小規模および大規模アレイで最速。
連結する場合は、挿入する場所のみを確認します。比較する必要のない他の大きな値(cmp = ab> 0)。
4つの数値の場合、システム4〜5 cmp(〜4.6)または3〜6 cmp(〜4.9)を使用できます。バブルソートは6cmp(6)を使用します。大きな数の遅いコードには多くのcmpがあります。
このコードは5cmpを使用します(MSLソートではありません):
if (cmp(arr[n][i+0],arr[n][i+1])>0) {swap(n,i+0,i+1);} if (cmp(arr[n][i+2],arr[n][i+3])>0) {swap(n,i+2,i+3);} if (cmp(arr[n][i+0],arr[n][i+2])>0) {swap(n,i+0,i+2);} if (cmp(arr[n][i+1],arr[n][i+3])>0) {swap(n,i+1,i+3);} if (cmp(arr[n][i+1],arr[n][i+2])>0) {swap(n,i+1,i+2);}

プリンシパルMSL 9 8 7 6 5 4 3 2 1 0 89 67 45 23 01 ... concat two sorted lists, list length = 1 6789 2345 01 ... concat two sorted lists, list length = 2 23456789 01 ... concat two sorted lists, list length = 4 0123456789 ... concat two sorted lists, list length = 8

jsコード

function sortListMerge_2a(cmp)	
{
var step, stepmax, tmp, a,b,c, i,j,k, m,n, cycles;
var start = 0;
var end   = arr_count;
//var str = '';
cycles = 0;
if (end>3)
	{
	stepmax = ((end - start + 1) >> 1) << 1;
	m = 1;
	n = 2;
	for (step=1;step<stepmax;step<<=1)	//bounds 1-1, 2-2, 4-4, 8-8...
		{
		a = start;
		while (a<end)
			{
			b = a + step;
			c = a + step + step;
			b = b<end ? b : end;
			c = c<end ? c : end;
			i = a;
			j = b;
			k = i;
			while (i<b && j<c)
				{
				if (cmp(arr[m][i],arr[m][j])>0)
					{arr[n][k] = arr[m][j]; j++; k++;}
				else	{arr[n][k] = arr[m][i]; i++; k++;}
				}
			while (i<b)
				{arr[n][k] = arr[m][i]; i++; k++;
}
			while (j<c)
				{arr[n][k] = arr[m][j]; j++; k++;
}
			a = c;
			}
		tmp = m; m = n; n = tmp;
		}
	return m;
	}
else
	{
	// sort 3 items
	sort10(cmp);
	return m;
	}
}

于 2017-01-12T14:26:11.563 に答える
0

パーティーに遅れるかもしれませんが、少なくとも私の貢献は新しいアプローチです。

  • コードは実際にインライン化する必要があります
  • インラインでもブランチが多すぎます
  • 分析部分は基本的にO(N(N-1))であり、N=6の場合は問題ないようです。
  • のコストswapが高くなると、コードはより効果的になる可能性があります(のコストはcompare
  • 静的関数がインライン化されていることを信頼しています。
  • この方法はランクソートに関連しています
    • ランクの代わりに、相対ランク(オフセット)が使用されます。
    • ランクの合計は、任意の順列グループのすべてのサイクルでゼロです。
    • 2つの要素を使用する代わりにSWAP()、サイクルが追跡され、1つの一時と1つの(レジスタ->レジスタ)スワップ(新しい<-古い)のみが必要です。

更新:コードを少し変更しました。CコードをコンパイルするためにC++コンパイラを使用する人もいます...

#include <stdio.h>

#if WANT_CHAR
typedef signed char Dif;
#else
typedef signed int Dif;
#endif

static int walksort (int *arr, int cnt);
static void countdifs (int *arr, Dif *dif, int cnt);
static void calcranks(int *arr, Dif *dif);

int wsort6(int *arr);

void do_print_a(char *msg, int *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
        fprintf(stderr, " %3d", *arr);
        }
fprintf(stderr,"\n");
}

void do_print_d(char *msg, Dif *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
        fprintf(stderr, " %3d", (int) *arr);
        }
fprintf(stderr,"\n");
}

static void inline countdifs (int *arr, Dif *dif, int cnt)
{
int top, bot;

for (top = 0; top < cnt; top++ ) {
        for (bot = 0; bot < top; bot++ ) {
                if (arr[top] < arr[bot]) { dif[top]--; dif[bot]++; }
                }
        }
return ;
}
        /* Copied from RexKerr ... */
static void inline calcranks(int *arr, Dif *dif){

dif[0] =     (arr[0]>arr[1])+(arr[0]>arr[2])+(arr[0]>arr[3])+(arr[0]>arr[4])+(arr[0]>arr[5]);
dif[1] = -1+ (arr[1]>=arr[0])+(arr[1]>arr[2])+(arr[1]>arr[3])+(arr[1]>arr[4])+(arr[1]>arr[5]);
dif[2] = -2+ (arr[2]>=arr[0])+(arr[2]>=arr[1])+(arr[2]>arr[3])+(arr[2]>arr[4])+(arr[2]>arr[5]);
dif[3] = -3+ (arr[3]>=arr[0])+(arr[3]>=arr[1])+(arr[3]>=arr[2])+(arr[3]>arr[4])+(arr[3]>arr[5]);
dif[4] = -4+ (arr[4]>=arr[0])+(arr[4]>=arr[1])+(arr[4]>=arr[2])+(arr[4]>=arr[3])+(arr[4]>arr[5]);
dif[5] = -(dif[0]+dif[1]+dif[2]+dif[3]+dif[4]);
}

static int walksort (int *arr, int cnt)
{
int idx, src,dst, nswap;

Dif difs[cnt];

#if WANT_REXK
calcranks(arr, difs);
#else
for (idx=0; idx < cnt; idx++) difs[idx] =0;
countdifs(arr, difs, cnt);
#endif
calcranks(arr, difs);

#define DUMP_IT 0
#if DUMP_IT
do_print_d("ISteps ", difs, cnt);
#endif

nswap = 0;
for (idx=0; idx < cnt; idx++) {
        int newval;
        int step,cyc;
        if ( !difs[idx] ) continue;
        newval = arr[idx];
        cyc = 0;
        src = idx;
        do      {
                int oldval;
                step = difs[src];
                difs[src] =0;
                dst = src + step;
                cyc += step ;
                if(dst == idx+1)idx=dst;
                oldval = arr[dst];
#if (DUMP_IT&1)
                fprintf(stderr, "[Nswap=%d] Cyc=%d Step=%2d Idx=%d  Old=%2d New=%2d #### Src=%d Dst=%d[%2d]->%2d <-- %d\n##\n"
                        , nswap, cyc, step, idx, oldval, newval
                        , src, dst, difs[dst], arr[dst]
                        , newval  );
                do_print_a("Array ", arr, cnt);
                do_print_d("Steps ", difs, cnt);
#endif

                arr[dst] = newval;
                newval = oldval;
                nswap++;
                src = dst;
                } while( cyc);
        }

return nswap;
}
/*************/
int wsort6(int *arr)
{
return walksort(arr, 6);
}
于 2018-02-01T01:00:50.510 に答える
0
//Bruteforce compute unrolled count dumbsort(min to 0-index)
void bcudc_sort6(int* a)
{
    int t[6] = {0};
    int r1,r2;

    r1=0;
    r1 += (a[0] > a[1]);
    r1 += (a[0] > a[2]);
    r1 += (a[0] > a[3]);
    r1 += (a[0] > a[4]);
    r1 += (a[0] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[0];

    r2=0;
    r2 += (a[1] > a[0]);
    r2 += (a[1] > a[2]);
    r2 += (a[1] > a[3]);
    r2 += (a[1] > a[4]);
    r2 += (a[1] > a[5]);
    while(t[r2]){r2++;} 
    t[r2] = a[1];

    r1=0;
    r1 += (a[2] > a[0]);
    r1 += (a[2] > a[1]);
    r1 += (a[2] > a[3]);
    r1 += (a[2] > a[4]);
    r1 += (a[2] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[2];

    r2=0;
    r2 += (a[3] > a[0]);
    r2 += (a[3] > a[1]);
    r2 += (a[3] > a[2]);
    r2 += (a[3] > a[4]);
    r2 += (a[3] > a[5]);
    while(t[r2]){r2++;} 
    t[r2] = a[3];

    r1=0;
    r1 += (a[4] > a[0]);
    r1 += (a[4] > a[1]);
    r1 += (a[4] > a[2]);
    r1 += (a[4] > a[3]);
    r1 += (a[4] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[4];

    r2=0;
    r2 += (a[5] > a[0]);
    r2 += (a[5] > a[1]);
    r2 += (a[5] > a[2]);
    r2 += (a[5] > a[3]);
    r2 += (a[5] > a[4]);
    while(t[r2]){r2++;} 
    t[r2] = a[5];

    a[0]=t[0];
    a[1]=t[1];
    a[2]=t[2];
    a[3]=t[3];
    a[4]=t[4];
    a[5]=t[5];
}

static __inline__ void sort6(int* a)
{
    #define wire(x,y); t = a[x] ^ a[y] ^ ( (a[x] ^ a[y]) & -(a[x] < a[y]) ); a[x] = a[x] ^ t; a[y] = a[y] ^ t;
    register int t;

    wire( 0, 1); wire( 2, 3); wire( 4, 5);
    wire( 3, 5); wire( 0, 2); wire( 1, 4);
    wire( 4, 5); wire( 2, 3); wire( 0, 1); 
    wire( 3, 4); wire( 1, 2); 
    wire( 2, 3);

    #undef wire
}
于 2019-02-20T14:46:39.323 に答える
-1

ええと、それが6つの要素だけで、並列処理を活用できる場合、条件付き分岐を最小限に抑えたい場合などです。すべての組み合わせを生成して順序をテストしないのはなぜですか。一部のアーキテクチャでは、かなり高速になる可能性があります(メモリが事前に割り当てられている限り)

于 2011-08-24T15:04:33.423 に答える
-1

使用法cmp==0で4つのアイテムを並べ替えます。cmpの数は約4.34(FFネイティブの数は約4.52)ですが、リストのマージよりも3倍の時間がかかります。ただし、大きな数値や大きなテキストがある場合は、cmp操作を少なくする方がよいでしょう。編集:バグを修正しました

オンラインテストhttp://mlich.zam.slu.cz/js-sort/x-sort-x2.htm

function sort4DG(cmp,start,end,n) // sort 4
{
var n     = typeof(n)    !=='undefined' ? n   : 1;
var cmp   = typeof(cmp)  !=='undefined' ? cmp   : sortCompare2;
var start = typeof(start)!=='undefined' ? start : 0;
var end   = typeof(end)  !=='undefined' ? end   : arr[n].length;
var count = end - start;
var pos = -1;
var i = start;
var cc = [];
// stabilni?
cc[01] = cmp(arr[n][i+0],arr[n][i+1]);
cc[23] = cmp(arr[n][i+2],arr[n][i+3]);
if (cc[01]>0) {swap(n,i+0,i+1);}
if (cc[23]>0) {swap(n,i+2,i+3);}
cc[12] = cmp(arr[n][i+1],arr[n][i+2]);
if (!(cc[12]>0)) {return n;}
cc[02] = cc[01]==0 ? cc[12] : cmp(arr[n][i+0],arr[n][i+2]);
if (cc[02]>0)
    {
    swap(n,i+1,i+2); swap(n,i+0,i+1); // bubble last to top
    cc[13] = cc[23]==0 ? cc[12] : cmp(arr[n][i+1],arr[n][i+3]);
    if (cc[13]>0)
        {
        swap(n,i+2,i+3); swap(n,i+1,i+2); // bubble
        return n;
        }
    else    {
    cc[23] = cc[23]==0 ? cc[12] : (cc[01]==0 ? cc[30] : cmp(arr[n][i+2],arr[n][i+3]));  // new cc23 | c03 //repaired
        if (cc[23]>0)
            {
            swap(n,i+2,i+3);
            return n;
            }
        return n;
        }
    }
else    {
    if (cc[12]>0)
        {
        swap(n,i+1,i+2);
        cc[23] = cc[23]==0 ? cc[12] : cmp(arr[n][i+2],arr[n][i+3]); // new cc23
        if (cc[23]>0)
            {
            swap(n,i+2,i+3);
            return n;
            }
        return n;
        }
    else    {
        return n;
        }
    }
return n;
}
于 2017-01-13T08:55:32.307 に答える