是否有比qsort更快的排序程序?

时间:2012-03-23 21:27:11

标签: c++ sorting

这不是一个算法问题,而是一个实现问题。

我的数据结构如下:

struct MyStruct {
   float val;
   float val2;
   int idx;
}

我浏览了大约4000万个元素的数组,并将'val'字段指定为元素,并将'idx'字段指定为索引。

我正在打电话:

MyStruct* theElements = new MyStruct[totalNum];
qsort(theElements, totalNum, sizeof(MyStruct), ValOrdering);

然后,一旦我填写val2,用

反转程序
qsort(theElements, totalNum, sizeof(MyStruct), IndexOrdering);

,其中

static int ValOrdering(const void* const v1, const void* const v2)
{
  if (((struct MyStruct*) v1)->val < ((struct MyStruct*) v2)->val)
    return -1;

  if (((struct MyStruct*) v1)->val> ((struct MyStruct*) v2)->val)
    return 1;

  return 0;
}

static int IndexOrdering(const void* const v1, const void* const v2)
{
  return ((struct MyStruct*) v1)->idx- ((struct MyStruct*) v2)->idx;
}

此设置需要4秒才能执行这两种排序。对于3Ghz i5处理器来说,4秒钟似乎需要很长时间才能使用4000万个元素。有更快的方法吗?我正在将vs2010与英特尔编译器一起使用(它有各种各样的结构,但不能超过我能看到的结构)。

更新:使用std :: sort剃须大约0.4秒的运行时间,称为:

std::sort(theElements, theElements + totalPixels, ValOrdering);
std::sort(theElements, theElements + totalPixels, IndexOrdering);

bool GradientOrdering(const MyStruct& i, const MyStruct& j){
    return i.val< j.val;
}
bool IndexOrdering(const MyStruct& i, const MyStruct& j){
    return i.idx< j.idx;
}

在谓词中添加'inline'关键字似乎并不重要。由于我有,并且规范允许,四核机器,我接下来会检查某种多线程排序。

更新2 :在@SirGeorge和@stark之后,我看了一下通过指针重定向完成的单一排序:

bool GradientOrdering(MyStruct* i, MyStruct* j){
    return i->val< j->val;
}
bool IndexOrdering(MyStruct* i, MyStruct* j){
    return i->idx< j->idx;
} 

即使只有一个排序调用(对GradientOrdering例程),生成的算法需要5秒,比qsort方法长1秒。看起来std :: sort现在正在赢。

更新3 :看起来英特尔的tbb::parallel_sort是赢家,在我的系统上将单一运行时间降低到0.5秒(因此,两者均为1.0秒,这意味着对于两者而言,它从原来的4.0s开始很好地扩展。我尝试使用Microsoft here提出的并行功能,但由于我已经在使用tbb而parallel_sort的语法与std::sort的语法相同,我可以使用我的较早的std::sort比较器可以完成所有工作。

我还使用了@ gbulmer的建议(实际上,击中我头上的实现),我已经有了原始的indeces,所以我只需要分配第二个数组,而不是第二次排序。从第一个回到排序顺序。我可以放弃这种内存使用,因为我只在具有至少4 GB RAM的64位机器上进行部署(很好地提前完成这些规范);没有这方面的知识,第二种方式是必要的。

@ gbulmer的建议提供了最快的速度,但最初的问题是关于最快的排序。 std::sort是最快的单线程,parallel_sort是最快的多线程,但没有人给出答案,所以我给了@gbulmer支票。

6 个答案:

答案 0 :(得分:14)

一般来说,位于std::sort的C ++ algorithm将超过qsort,因为它允许编译器优化对函数指针的间接调用,并使编译器更容易执行内联。然而,这只是一个恒定因素加速; qsort已经使用了非常快速的排序算法。

请注意,如果您决定切换到std::sort,则必须更改比较仿函数。 std::sort接受一个简单的小于比较返回bool,而std::qsort接受一个仿函数返回-1,0或1,具体取决于输入。

答案 1 :(得分:4)

与缓存相比,数据集非常庞大,因此缓存到内存有限。

使用间接会使这更糟糕,因为指针有缓存,并且以更随机的顺序访问内存,即比较不是与邻居进行比较。该程序正在对付CPU中的任何预取机制

考虑将结构分成两个数组中的两个结构。

作为一项实验,比较传递1和传递1,其中结构仅为{ float val; int idx; };

如果是缓存和带宽限制,则应该产生显着差异。

如果缓存局部性是一个关键问题,那么可能值得考虑多路合并或Shell排序;任何改善地方的事情。

尝试对记录的缓存大小子集进行排序,然后进行多路合并排序(可能值得查看处理器缓存管理器规范,看看是否可以预测预取流的数量是否清楚。同样,减少数据集的大小,通过减少从RAM流入的结构的大小可能是q赢家。

idx字段是如何派生的?听起来它是阵列中的原始位置。它是原始记录的索引吗?

如果是这种情况,只需分配第二个数组,然后将第一个数组复制到第二个数组中:

struct { float val; float val2; int idx } sortedByVal[40000000];
struct { float val; float val2 } sortedbyIdx[40000000];

for (int i=0; i<40000000; ++i) {
    sortedbyIdx[sortedByVal[i].idx].val = sortedByVal[i].val;
    sortedbyIdx[sortedByVal[i].idx].val2 = sortedByVal[i].val2;
}

没有第二种。如果是这种情况,请将val2值的分配与此传递合并。

修改

我很好奇,关于相对表现,所以我写了一个程序来比较'库'C排序函数,qsort,mergesort,heapsort,还比较排序到idx和副本到idx。它还重新排序已排序的值,以便对其进行处理。这也很有趣。我没有实现并测试Shell排序,它通常在实践中胜过qsort。

程序使用命令行参数来选择排序,是否按idx排序,或只是复制。代码:http://pastebin.com/Ckc4ixNp

运行时的抖动非常清楚。我应该使用CPU时钟,完成多次运行,并提供更好的结果,但这是“为读者练习”。

我在旧款MacBook Pro 2.2GHz Intel Core 2 Duo上运行了这款产品。 一些时间是OS C特定的。

计时(稍微重新格式化):

qsort(data, number-of-elements=40000000, element-size=12)
Sorting by val - duration =            16.304194
Re-order to idx by copying - duration = 2.904821
Sort in-order data - duration =         2.013237
Total duration = 21.222251
User Time:       20.754574
System Time:      0.402959

mergesort(data, number-of-elements=40000000, element-size=12)
Sorting by val - duration =            25.948651
Re-order to idx by copying - duration = 2.907766
Sort in-order data - duration =         0.593022
Total duration = 29.449438
User Time:       28.428954
System Time:      0.973349

heapsort(data, number-of-elements=40000000, element-size=12)
Sorting by val - duration =            72.236463
Re-order to idx by copying - duration = 2.899309
Sort in-order data - duration =        28.619173
Total duration = 103.754945
User Time:       103.107129
System Time:       0.564034

警告:这些是单次运行。需要进行许多运行才能获得合理的统计数据。

pastebin上的代码实际上对“缩小的大小”,8字节数组进行了排序。在第一次传递时,只需要val和idx,并且当添加val2时复制数组,第一个数组中不需要val2。这种优化使得排序函数可以复制较小的结构,并且还可以在缓存中容纳更多结构,这很好。我很失望,这给qsort带来了几个百分点的提升。我将其解释为qsort快速将块分类为适合缓存的大小。

相同的缩小尺寸策略使得heapsort的改进率提高了25%以上。

8字节结构的时序,没有val2:

qsort(data, number-of-elements=40000000, element-size=8)
Sorting by val - duration =            16.087761
Re-order to idx by copying - duration = 2.858881
Sort in-order data - duration =         1.888554
Total duration = 20.835196
User Time:       20.417285
System Time:      0.402756

mergesort(data, number-of-elements=40000000, element-size=8)
Sorting by val - duration =            22.590726
Re-order to idx by copying - duration = 2.860935
Sort in-order data - duration =         0.577589
Total duration = 26.029249
User Time:       25.234369
System Time:      0.779115

heapsort(data, number-of-elements=40000000, element-size=8)
Sorting by val - duration =            52.835870
Re-order to idx by copying - duration = 2.858543
Sort in-order data - duration =        24.660178
Total duration = 80.354592
User Time:       79.696220
System Time:      0.549068

警告:这些是单次运行。需要进行许多运行才能获得合理的统计数据。

答案 2 :(得分:3)

按索引排序时,radix sort可能比快速排序更快。您可能希望在2的幂的基础上执行它(因此您可以使用按位运算而不是模数)。

答案 3 :(得分:3)

std::sort()应该快10%以上。但是,您需要两件事:

  1. 使用函数指针从编译器获取英雄,以检测该函数是否可以内联。具有内联函数调用运算符的函数对象相对容易内联。
  2. 在调试模式std::sort()的核心将不会被优化qsort()经过优化:尝试在发布模式下进行编译。

答案 4 :(得分:1)

所有的排序算法都是已知的。它们很容易实现。对他们进行基准测试。

在所有情况下,快速排序可能不是最快的,但平均效率非常高。然而,有4000万条记录很多,在3-4秒内排序并不是闻所未闻。

修改

我将总结一下我的评论:已经证明,在Turing(这里拼写正确!!!)模型下,比较排序算法受Ω(n log n)限制。因此,复杂性方面没有太多可以改进的地方,但魔鬼在于细节。要发现复杂性等效算法的性能差异,您需要对它们进行基准测试并查看结果。

但是,如果您对数据有一些额外的了解(例如 - idx将在某个预设范围内并且范围相对较小),您可以使用非比较排序的算法,并且具有复杂性提高。您仍然应该进行基准测试,以确保实际上对您的数据进行了改进,但对于大量,Ω(n log n)和Ω(n)之间的差异可能会很明显。这种算法的一个例子是bucket-sort。

要获得更全面的列表和复杂性分析,请启动here

答案 5 :(得分:1)

现在你要对array of structures进行排序,这意味着数组中的每个交换都是至少两个赋值(复制整个结构)。您可以尝试对指向结构的指针数组进行排序,这将节省大量复制(只是复制指针),但您会使用更多内存。排序指针数组的另一个优点是你可能有一些(每个都按不同的方式排序) - 再次需要更多的内存。然而,额外的指针间接可能是昂贵的。您也可以尝试使用其他人一起提出的两种方法:std::qsort指针数组 - 并查看您的情况是否有任何加速。