c++ - 使用比较器网络对固定长度数组进行非常快速的排序

标签 c++ arrays sorting template-meta-programming sorting-network

我有一些性能关键代码,涉及在 C++ 中对具有大约 3 到 10 个元素的非常短的固定长度数组进行排序(参数在编译时更改)。

在我看来,专门针对每个可能的输入大小的静态排序网络可能是一种非常有效的方法:我们进行所有必要的比较以确定我们处于哪种情况,然后进行最佳数量的交换以对数组进行排序。

为了应用这一点,我们使用了一些模板魔法来推断数组长度并应用正确的网络:

#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::sort ?
  • 是否有一个简单的地方可以获取实现这种排序网络的代码?
  • 也许可以使用模板魔法静态生成这样的排序网络..

现在我只使用带有静态模板参数的插入排序(如上),希望它会鼓励展开和其他编译时优化。

欢迎您的想法。


更新: 我编写了一些测试代码来比较“静态”插入短和 std::sort。 (当我说静态时,我的意思是数组大小是固定的并在编译时推导出来(可能允许循环展开等)。 我至少获得了 20% 的 NET 改进(请注意,生成包含在时间中)。平台:clang,OS X 10.9。

代码在这里https://github.com/rosshemsley/static_sorting如果您想将其与您的 stdlib 实现进行比较。

我还没有为比较器网络分类器找到一套不错的实现。


最佳答案

这是一个使用 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;
}

基准测试

以下基准测试使用 clang -O3 编译并在我 2012 年中期的 macbook air 上运行。

对 100 万个数组进行排序的时间(以毫秒为单位)。
大小为 2、4、8 的数组的毫秒数分别为 1.943、8.655、20.246。
C++ Templated Bose-Nelson Static Sort timings

以下是 6 个元素的小型数组的平均时钟。基准代码和示例可以在这个问题中找到:
Fastest sort of fixed length 6 int array

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 

对于 6 个元素,它的执行速度与问题中最快的示例一样快。

可以找到用于基准测试的代码 here .

它包含更多功能和进一步优化,可在真实数据上实现更强大的性能。

关于c++ - 使用比较器网络对固定长度数组进行非常快速的排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19790522/

相关文章:

java - 如何删除数组中的 "null"个元素?

java - 这部分代码会在归并排序中执行吗?

Javascript自定义排序算法根据另一个数组

linux - 在linux中对多个文件进行排序

c++ - QImage::Format_mono 到 .png 和 QGraphicsScene

c# - 套接字服务器应用程序的选择 : C/C++ or C#

c++ - 在 C++ 中是否可以在正文中声明属性?

c++ - 如何恢复 QMainWindow 的初始 centralwidget?

java - 错误的结果 一旦我处理 try and catch

c - 尽管有足够的可用内存,但堆栈溢出