Baru-baru ini saya menulis sebuah kelas kecil yang menggunakan algoritma Bose-Nelson untuk menghasilkan jaringan sortir pada waktu kompilasi.
Ini dapat digunakan untuk membuat sort yang sangat cepat untuk 10 angka.
/**
* 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;
}
Perhatikan bahwa alih-alih if (compare) swap
pernyataan, kami secara eksplisit memberi kode pada operator ternary untuk min dan maks. Ini untuk membantu mendorong kompiler menggunakan kode branchless.
Tolak ukur
Tolok ukur berikut dikompilasi dengan dentang -O3 dan berjalan pada pertengahan 2012 macbook air saya.
Menyortir data acak
Membandingkannya dengan kode DarioP, berikut adalah jumlah milidetik yang diambil untuk mengurutkan 1 juta array int 32-bit ukuran 10:
Hardcoded Sort Net 10: 88.774 ms
Templated Bose-Nelson sort 10: 27.815 ms
Menggunakan pendekatan templated ini, kita juga dapat menghasilkan jaringan sortir pada waktu kompilasi untuk sejumlah elemen lainnya.
Waktu (dalam milidetik) untuk mengurutkan 1 juta array dari berbagai ukuran.
Jumlah milidetik untuk array ukuran 2, 4, 8 masing-masing adalah 1.943, 8.655, 20.246.
Penghargaan untuk Glenn Teitelbaum untuk jenis penyisipan yang tidak gulungan.
Berikut adalah jam rata-rata per sort untuk array kecil dari 6 elemen. Kode dan contoh benchmark dapat ditemukan pada pertanyaan ini:
Jenis tercepat dari array dengan panjang tetap 6 int
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
Ia melakukan secepat contoh tercepat dalam pertanyaan untuk 6 elemen.
Kinerja untuk menyortir data yang diurutkan
Seringkali, array input mungkin sudah diurutkan atau sebagian besar diurutkan.
Dalam kasus seperti itu, jenis penyisipan bisa menjadi pilihan yang lebih baik.
Anda mungkin ingin memilih algoritma pengurutan yang sesuai tergantung pada data.
Kode yang digunakan untuk tolok ukur dapat ditemukan di sini .
if
pernyataan bersarang harus bekerja yang terbaik. Hindari loop.