这不是一个算法问题,而是一个实现问题。
我的数据结构如下:
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支票。
答案 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%以上。但是,您需要两件事:
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
指针数组 - 并查看您的情况是否有任何加速。