使用比较器网络对固定长度阵列进行快速排序 我有一些性能关键代码,它涉及在C++中排序一个长度在3到10个元素之间的非常短的固定长度数组(编译时参数发生变化)。
我突然想到,专门针对每种可能的输入大小的静态排序网络可能是一种非常有效的方法:我们进行所有必要的比较,以确定我们处于哪种情况,然后进行最佳交换数,以对阵列进行排序 要应用此功能,我们使用一点模板魔术来推断数组长度并应用正确的网络:使用比较器网络对固定长度阵列进行快速排序 我有一些性能关键代码,它涉及在C++中排序一个长度在3到10个元素之间的非常短的固定长度数组(编译时参数发生变化)。,c++,arrays,sorting,template-meta-programming,sorting-network,C++,Arrays,Sorting,Template Meta Programming,Sorting Network,我突然想到,专门针对每种可能的输入大小的静态排序网络可能是一种非常有效的方法:我们进行所有必要的比较,以确定我们处于哪种情况,然后进行最佳交换数,以对阵列进行排序 要应用此功能,我们使用一点模板魔术来推断数组长度并应用正确的网络: #include <iostream> using namespace std; template< int K > void static_sort(const double(&array)[K]) { cout <&
#include <iostream>
using namespace std;
template< int K >
void static_sort(const double(&array)[K])
{
cout << "General static sort\n" << endl;
}
template<>
void static_sort<3>(const double(&array)[3])
{
cout << "Static sort for K=3" << endl;
}
int main()
{
double array[3];
// performance critical code.
// ...
static_sort(array);
// ...
}
#包括
使用名称空间std;
模板
无效静态\u排序(常量双精度(&数组)[K])
{
让我分享一些想法
有人对这是否值得一试有什么看法吗
努力
要给出正确的答案是不可能的。您必须分析实际代码才能找到答案。
在我的实践中,当涉及到低级评测时,瓶颈总是不是我所想的
有人知道这种优化是否存在于任何标准中吗
例如,std::sort的实现
例如,VisualC++实现的代码> STD::排序< /COD>使用小向量的插入排序。我不知道使用最佳排序网络的实现。
也许可以生成这样的排序网络
静态使用模板魔术
有分拣网络的生成算法,如Boeer-Nelson、希巴德和Batcher算法。由于C++模板是图灵完成,可以使用TMP实现它们。然而,这些算法不能保证理论上最少的比较器数量,所以你可能想要对最佳网络进行硬编码。N的已知最佳或至少最佳长度比较器网络其他答案有趣且相当好,但我相信我可以提供一些额外的答案元素,每点:
- 这值得付出努力吗?如果您需要对小整数集合进行排序,并且对排序网络进行了优化,以尽可能利用某些指令,那么这可能值得付出努力。下图显示了使用不同排序算法对大小为0-14的一百万个
int
数组进行排序的结果。如下所示:您可以看到,如果您确实需要,分拣网络可以提供显著的加速
- 没有标准的
std::sort
实现,我知道它使用排序网络;如果没有微调,它们可能会比直接插入排序慢。libc++的std::sort
有专门的算法可以一次对0到5个值进行排序,但它们也不使用排序网络。这是我所知道的唯一的排序算法也就是说,这篇研究论文建议,排序网络可以用来对小数组进行排序,或者改进递归排序算法(如快速排序),但前提是它们必须经过微调,以利用特定的硬件指令
该算法是一种自底向上的mergesort算法,显然在第一遍使用SIMD指令实现的双音排序网络。显然,对于某些标量类型,该算法可能比标准库算法更快
- 我实际上可以提供这样的信息,原因很简单,我开发了一个能够提供大小为0到32的高效排序网络,该网络实现了上一节中描述的优化。我在第一节中使用它生成了图形。我仍在处理库中要提供的排序网络部分去规模最优、深度最优和交换最优网络。小的最优排序网络是用蛮力找到的,而大的排序网络使用的是Literature的结果
请注意,库中没有任何排序算法直接使用排序网络,但您可以对其进行调整,以便在为排序算法指定一个小的
std::array
或一个小的固定大小的C数组时,可以选择一个排序网络:
using namespace cppsort;
// Sorters are function objects that can be
// adapted with sorter adapters from the
// library
using sorter = small_array_adapter<
std_sorter,
sorting_network_sorter
>;
// Now you can use it as a function
sorter sort;
// Instead of a size-agnostic sorting algorithm,
// sort will use an optimal sorting network for
// 5 inputs since the bound of the array can be
// deduced at compile time
int arr[] = { 2, 4, 7, 9, 3 };
sort(arr);
使用名称空间cppsort;
//分拣机是可以
//与来自的分拣机适配器相适应
//图书馆
使用分拣机=小型阵列适配器<
标准分拣机,
分拣网络分拣机
>;
//现在您可以将其用作函数
分拣机分拣;
//而不是大小无关的排序算法,
//分拣将使用最佳的分拣网络进行分拣
//5个输入,因为数组的边界可以
//在编译时推导
int arr[]={2,4,7,9,3};
排序(arr);
如上所述,该库为内置整数提供了高效的排序网络,但如果需要对其他内容的小数组进行排序,则可能运气不佳(例如,我最新的基准测试表明,即使对于long-long int
,它们也不比直接插入排序好)
- 您可能可以使用模板元编程来生成任意大小的排序网络,但没有已知的算法可以生成最佳排序网络,因此您不妨手工编写最佳排序网络。我认为简单算法生成的排序网络实际上无法提供可用且高效的网络(Batcher的奇偶排序和成对排序网络可能是唯一可用的)[另一个答案似乎表明生成的网络实际上可以工作]
这里有一个小类,它使用Bose Nelson算法在编译时生成排序网络
/**
* 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 = 32 };
// 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;
}
对于6个元素,它的执行速度与问题中最快的示例一样快
可以找到用于基准测试的代码
它包括更多的功能和进一步的优化,以便在真实数据上获得更强健的性能。您要排序的值是什么?它们是否在任何固定范围内?我的值实际上恰好是[0,2pi]中的角度。但我猜我的id
Direct call to qsort library function : 342.26
Naive implementation (insertion sort) : 136.76
Insertion Sort (Daniel Stutzbach) : 101.37
Insertion Sort Unrolled : 110.27
Rank Order : 90.88
Rank Order with registers : 90.29
Sorting Networks (Daniel Stutzbach) : 93.66
Sorting Networks (Paul R) : 31.54
Sorting Networks 12 with Fast Swap : 32.06
Sorting Networks 12 reordered Swap : 29.74
Reordered Sorting Network w/ fast swap : 25.28
Templated Sorting Network (this class) : 25.01