it-swarm.com.de

Schnellste Möglichkeit, 10 Zahlen zu sortieren? (Zahlen sind 32 Bit)

Ich löse ein Problem und es geht darum, 10 Zahlen (int32) sehr schnell zu sortieren. Meine Anwendung muss Millionen von Zahlen so schnell wie möglich sortieren. Ich probiere einen Datensatz von Milliarden von Elementen aus und jedes Mal, wenn ich 10 Zahlen daraus auswählen muss (vereinfacht) und sortieren (und Schlussfolgerungen aus der sortierten 10-Element-Liste ziehen).

Derzeit verwende ich die Einfügungssortierung, aber ich kann mir vorstellen, dass ich einen sehr schnellen benutzerdefinierten Sortieralgorithmus für mein spezifisches Problem von 10 Zahlen implementieren könnte, der die Einfügungssortierung schlagen würde.

Hat jemand eine Idee, wie man dieses Problem angehen soll?

207
bodacydo

(Folgen Sie dem Vorschlag von HelloWorld, die Sortier-Netzwerke zu untersuchen.) 

Es scheint, dass ein 29-Vergleich/Swap-Netzwerk der schnellste Weg ist, eine Sortierung mit 10 Eingängen durchzuführen. Ich habe das von Waksman 1969 entdeckte Netzwerk für dieses Beispiel in Javascript verwendet, das direkt in C übersetzt werden sollte, da es sich lediglich um eine Liste von if-Anweisungen, Vergleichen und Swaps handelt.

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]));

Hier ist eine grafische Darstellung des Netzwerks, unterteilt in unabhängige Phasen.
 10-input sorting network (Waksman, 1969)
Um die parallele Verarbeitung zu nutzen, kann die 5-4-3-4-4-4-3-2-Gruppierung in eine 4-4-4-4-4-4-3-2-Gruppierung geändert werden.
 10-input sorting network (Waksman, 1969) re-grouped

209
m69

Wenn Sie sich mit dieser festen Größe auseinandersetzen, schauen Sie unter Sorting Networks . Diese Algorithmen haben eine feste Laufzeit und sind unabhängig von ihrer Eingabe. Für Ihren Anwendungsfall haben Sie keinen solchen Aufwand, den einige Sortieralgorithmen haben.

Bitonic Sort ist eine Implementierung eines solchen Netzwerks. Dies funktioniert am besten mit len ​​(n) <= 32 auf einer CPU. Bei größeren Eingaben könnten Sie an eine GPU denken .. /. https://en.wikipedia.org/wiki/Sorting_network

Übrigens, eine gute Seite, um Sortieralgorithmen zu vergleichen, ist diese hier (obwohl der bitonic sort fehlt.

http://www.sorting-algorithms.com

87
seb-mtl

Verwenden Sie ein Sortier-Netzwerk, das Vergleiche in 4er-Gruppen enthält, sodass Sie dies in SIMD-Registern tun können. Ein Paar gepackter min/max-Anweisungen implementiert eine gepackte Komparatorfunktion. Tut mir leid, ich habe momentan keine Zeit, nach einer Seite zu suchen, an die ich mich erinnere, aber ich hoffe, dass das Suchen bei SIMD- oder SSE -Netzwerken etwas auftaucht.

x86 SSE enthält gepackte 32-Bit-Integer-Min- und-Max-Anweisungen für Vektoren von vier 32-Bit-Ints. AVX2 (Haswell und später) haben die gleichen, jedoch für 256b-Vektoren von 8 Ints. Es gibt auch effiziente Shuffle-Anweisungen.

Wenn Sie viele unabhängige kleine Sortierungen haben, können Sie 4 oder 8 Sortierungen parallel mit Vektoren durchführen. Esp. Wenn Sie Elemente nach dem Zufallsprinzip auswählen (damit die zu sortierenden Daten im Speicher ohnehin nicht zusammenhängend sind), können Sie Zufallszahlen vermeiden und einfach in der gewünschten Reihenfolge vergleichen. 10 Register für alle Daten aus 4 (AVX2: 8) Listen mit 10 Ints lassen noch 6 Register für Scratch-Speicher übrig.

Vektorsortierungsnetzwerke sind weniger effizient, wenn Sie auch die zugehörigen Daten sortieren müssen. In diesem Fall scheint die effizienteste Methode die Verwendung eines gepackten Vergleichs zu sein, um eine Maske zu erhalten, deren Elemente geändert wurden, und diese Maske zu verwenden, um Vektoren (Bezugnahmen) auf verknüpfte Daten zu mischen.

33
Peter Cordes

Was ist mit einer abgewickelten Sortierung ohne Zweig?

#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

Die einzigen relevanten Zeilen sind die ersten beiden #define.

Es verwendet zwei Listen und überprüft die erste vollständig zehnmal, was eine schlecht implementierte Auswahlsorte darstellt, vermeidet jedoch Verzweigungen und Schleifen mit variabler Länge, die sich mit modernen Prozessoren und einem so kleinen Datensatz kompensieren können.


Benchmark

Ich habe mich mit dem Sortier-Netzwerk verglichen und mein Code scheint langsamer zu sein. Ich habe jedoch versucht, das Abrollen und die Kopie zu entfernen. Diesen Code ausführen:

#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;
}

Ich erhalte durchweg besseres Ergebnis für die verzweigungslose Sortierung im Vergleich zum Sortiernetzwerk.

$ 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
25
DarioP

Die Frage sagt nicht aus, dass dies eine Art webbasierte Anwendung ist. Das einzige, was mir ins Auge fiel, war:

Ich probiere einen Datensatz von Milliarden von Elementen aus und jedes Mal, wenn ich 10 Zahlen daraus auswählen muss (vereinfacht) und sortieren (und Schlussfolgerungen aus der sortierten 10-Element-Liste ziehen).

Als Software- und Hardware-Entwickler schreit dies absolut "FPGA" zu mir. Ich weiß nicht, welche Schlussfolgerungen Sie aus dem sortierten Zahlensatz ziehen müssen, oder woher die Daten stammen, aber ich weiß, dass es fast trivial wäre, irgendwo zwischen einhundert Millionen und einer Milliardezu verarbeiten Diese "Sortieren und Analysieren" -Operationen pro Sekunde. Ich habe in der Vergangenheit FPGA-gestützte DNA-Sequenzierungen durchgeführt. Es ist fast unmöglich, die enorme Rechenleistung von FPGAs zu übertreffen, wenn das Problem für diese Art von Lösung gut geeignet ist.

Auf einer bestimmten Ebene ist der einzige limitierende Faktor, wie schnell Sie Daten in einen FPGA laden können und wie schnell Sie diese ausgeben können.

Als Bezugspunkt habe ich einen Hochleistungs-Echtzeitbildprozessor entwickelt, der 32-Bit-RGB-Bilddaten mit einer Geschwindigkeit von etwa 300 Millionen Pixel pro Sekunde empfängt. Die Daten wurden durch FIR-Filter, Matrix-Multiplizierer, Nachschlagetabellen, räumliche Kantenerkennungsblöcke und eine Reihe anderer Operationen gestreamt, bevor sie am anderen Ende herauskamen. All dies auf einem relativ kleinen Xilinx-Virtex2-FPGA mit interner Taktung von etwa 33 MHz bis, wenn ich mich recht erinnere, 400 MHz. Ach ja, es gab auch eine DDR2-Controller-Implementierung und zwei DDR2-Speicherbänke.

Ein FPGA kann bei jedem Taktübergang eine Art von zehn 32-Bit-Zahlen ausgeben, während er mit Hunderten von MHz arbeitet. Zu Beginn des Vorgangs würde es zu einer kurzen Verzögerung kommen, da die Daten die Verarbeitungspipeline füllen. Danach sollten Sie in der Lage sein, ein Ergebnis pro Uhr zu erhalten. Oder mehr, wenn die Verarbeitung durch Replizieren der Sortier- und Analyse-Pipeline parallelisiert werden kann. Die Lösung ist im Prinzip fast trivial.

Der Punkt ist: Wenn die Anwendung nicht an einen PC gebunden ist und der Datenstrom und die Verarbeitung mit einer FPGA-Lösung (entweder als eigenständige Karte oder als Co-Prozessor-Karte in der Maschine) "kompatibel" sind, gibt es keine Möglichkeit, dies zu tun das erreichbare Leistungsniveau mit in jeder Sprache geschriebener Software unabhängig vom Algorithmus zu übertreffen.

EDIT:

Ich habe nur eine schnelle Suche durchgeführt und ein Papier gefunden, das für Sie nützlich sein könnte. Es sieht so aus, als ob es bis ins Jahr 2012 zurückreicht. Sie können heute (und sogar damals) eine viel bessere Leistung erzielen. Hier ist es:

Sortieren von Netzwerken auf FPGAs

20
martin's

Ich habe vor kurzem eine kleine Klasse geschrieben, die den Bose-Nelson-Algorithmus verwendet, um ein Sortier-Netzwerk zur Kompilierzeit zu erzeugen. 

Es kann verwendet werden, um eine sehr schnelle Sortierung für 10 Zahlen zu erstellen. 

/**
 * 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;
}

Beachten Sie, dass wir anstelle einer if (compare) swap-Anweisung ternäre Operatoren für min und max explizit auscodieren. Dies soll helfen, den Compiler dazu zu bewegen, verzweigungslosen Code zu verwenden.

Benchmarks

Die folgenden Benchmarks werden mit clang -O3 kompiliert und laufen auf meinem Macbook Air von Mitte 2012.

Zufallsdaten sortieren

Im Vergleich zu DarioPs Code wird hier die Anzahl der Millisekunden angegeben, die zum Sortieren von 1 Million 32-Bit-Int-Arrays der Größe 10 benötigt werden: 

Hardcoded Sort Net 10: 88,774 ms
Templated Bose-Nelson Sort 10: 27.815 ms

Mit diesem Ansatz können wir zum Zeitpunkt des Kompilierens auch Sortiernetzwerke für eine andere Anzahl von Elementen generieren. 

Zeit (in Millisekunden) zum Sortieren von 1 Million Arrays verschiedener Größen.
. Die Anzahl der Millisekunden für Arrays der Größe 2, 4, 8 beträgt 1.943, 8.655 bzw. 20.246.
C++ Templated Bose-Nelson Static Sort timings

Gutschriften an Glenn Teitelbaum für die aufgerollte Einfügungsart.

Hier sind die durchschnittlichen Uhren pro Sorte für kleine Arrays mit 6 Elementen. Der Benchmark-Code und die Beispiele finden Sie unter dieser Frage:
Schnellste Sorte mit fester Länge 6 int array

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

Es ist so schnell wie das schnellste Beispiel in der Frage für 6 Elemente. 

Leistung beim Sortieren sortierter Daten

Oft sind die Eingabearrays bereits sortiert oder meistens sortiert.
.__ In solchen Fällen kann die Sortierreihenfolge die bessere Wahl sein.

 enter image description here

Abhängig von den Daten möchten Sie möglicherweise einen geeigneten Sortieralgorithmus wählen.

Den für die Benchmarks verwendeten Code finden Sie hier .

9
Vectorized

Obwohl eine Netzwerksortierung gute Chancen hat, auf kleinen Arrays schnell zu sein, können Sie die Einfügungssortierung manchmal nicht unterbieten, wenn sie richtig optimiert ist. Zum Beispiel Batch Insert mit 2 Elementen:

{
    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;
}
5
warren

Sie können insertion sort vollständig abwickeln.

Um dies zu erleichtern, können rekursive templates ohne Funktionsaufwand verwendet werden. Da es bereits eine template ist, kann int auch ein template-Parameter sein. Dadurch werden auch andere Codierungs-Array-Größen als 10 erstellt.

Beachten Sie, dass zum Aufruf von int x[10] der Aufruf insert_sort<int, 9>::sort(x); lautet, da die Klasse den Index des letzten Elements verwendet. Dies könnte umhüllt sein, aber es wäre mehr Code zum Durchlesen. 

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
    }
};

In meinen Tests war dies schneller als in den Beispielen des Sortiernetzwerks.

3

Aus ähnlichen Gründen wie die von hier beschriebenen, sollten die folgenden Sortierfunktionen sort6_iterator() und sort10_iterator_local() gut funktionieren, wenn das Sortier-Netzwerk aus here entnommen wurde:

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
}

Um diese Funktion aufzurufen, habe ich einen std::vector-Iterator übergeben. 

0
Matthew K.

Eine Einfügungssortierung erfordert im Durchschnitt 29,6 Vergleiche, um 10 Eingänge mit einem besten Fall von 9 und einem schlechtesten von 45 zu sortieren (gegebene Eingabe in umgekehrter Reihenfolge).

Eine {9,6,1} -Shellsortierung erfordert durchschnittlich 25,5 Vergleiche, um 10 Eingaben zu sortieren. Der beste Fall ist 14 Vergleiche, der schlechteste ist 34 und das Sortieren einer umgekehrten Eingabe erfordert 22.

Die Verwendung von Shellsort anstelle von Einfügungssortierung reduziert den durchschnittlichen Fall um 14%. Obwohl der beste Fall um 56% erhöht wird, wird der ungünstigste Fall um 24% reduziert, was in Anwendungen von Bedeutung ist, in denen es wichtig ist, die Leistung im ungünstigsten Fall unter Kontrolle zu halten. Der umgekehrte Fall ist um 51% reduziert.

Da Sie mit der Einfügungssortierung vertraut zu sein scheinen, können Sie den Algorithmus als Sortiernetzwerk für {9,6} implementieren und anschließend die Einfügungssortierung ({1}) aktivieren:

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
0
Olof Forshell