大桌子的快速排序出奇地慢

Posted

技术标签:

【中文标题】大桌子的快速排序出奇地慢【英文标题】:strangely slow quicksort for large tables 【发布时间】:2014-12-30 15:07:09 【问题描述】:

我一直在做作业,比较一堆排序算法,我遇到了一个奇怪的现象。事情正如预期的那样:插入排序赢得了诸如 20 个整数的表之类的东西,否则快速排序优于堆排序和归并排序。最多包含 500,000 个整数的表(存储在内存中)。对于 5,000,000 个整数(仍存储在内存中),快速排序突然变得比堆排序和归并排序更糟。数字始终是均匀分布的随机数,windows 虚拟内存关闭。有人知道这可能是什么原因吗?

     void quicksortit(T *tab,int s) 
                   if (s==0 || s==1) return;
                   T tmp;
                   if (s==2) 
                      if (tab[0]>tab[1]) 
                                         tmp=tab[0];
                                         tab[0]=tab[1];
                                         tab[1]=tmp;
                                         
                      return;
                      
                   T pivot=tab[s-1];
                   T *f1,*f2;
                   f1=f2=tab;
                   for(int i=0;i<s;i++)
                           if (*f2>pivot)
                              f2++;
                           else 
                                tmp=*f1;
                                *f1=*f2;
                                *f2=tmp;
                                f1++; f2++;
                                
                   quicksortit(tab,(f1-1)-tab);
                   quicksortit(f1,f2-f1);
     ;

【问题讨论】:

我不知道您使用的是什么系统,但我的第一个猜测是由于数据集较大,您遇到了更多的处理器缓存未命中。 对于大型数组,选择一个好的枢轴值比简单地运行算法更重要。试试T pivot=tab[s/2]; 看看有什么帮助 每次运行代码时都会更改种子值,还是始终使用相同的初始 500 万大小数组? 你说“突然更糟”,但你没有具体说明有多突然,以及有多糟。例如,它在 4999999 时是否更快,然后在 5000000 时更差?如果逐渐恶化,您能否描述或展示性能曲线的样子? @jxd: 肯定是reseeded,2,000,000 qs 运行时间几乎是mergesort 的一半,3000,000 是一样的。 【参考方案1】:

可能是您的数组现在比 L3 缓存大。

快速排序分区操作将随机元素从数组的一端移动到另一端。典型的 Intel L3 缓存为 8MB。使用 5M 4 字节元素 - 您的数组为 20MB。你正在从它的一端写到另一端。

L3 之外的缓存未命中进入主内存,并且可能比更高级别的缓存未命中慢很多

到目前为止,您的整个排序操作完全在 CPU 内部运行。

【讨论】:

谢谢,可能是这样。我将在可能具有不同 CPU 的不同机器上检查此代码。我的是 i7-2630QM(6MB 4 核共享 l3 缓存)。 仍然让我感到困惑的是为什么快速排序与合并排序有很大不同。很明显,堆排序在使用 L3 缓存约束时会做得很糟糕——元素几乎以随机方式读取和写入。然而,在快速排序和合并排序中,表都是在本地访问的。快速排序需要维护缓存中原始表的一部分,靠近移动指针f1 和靠近f2。合并排序不仅需要在两个移动指针附近存储两个半数组,还需要至少存储它要合并到的表的一部分。【参考方案2】:

当数组中有许多重复项时,您的算法开始失败。您只在大值时注意到了这一点,因为您一直在为算法提供具有大跨度的随机值(我假设您使用 rand() 和:0 - RAND_MAX ),并且该问题仅出现在大数组中。

当您尝试对相同数字的数组进行排序时(尝试对 100000 个相同的数字进行排序,程序将崩溃),您将首先遍历整个数组,不必要地交换元素。然后你把数组一分为二,但是大数组只减了1:

                    v
quicksortit(tab,(f1-1)-tab);

因此,您的算法变为 O(n^2),并且您还消耗了大量的堆栈。在这种情况下,寻找更好的支点对您没有帮助,而是选择不存在此缺陷的 quicksort() 版本。

例如:

function quicksort(array)
    if length(array) > 1
        pivot := select middle, or a median of first, last and middle
        left := first index of array
        right := last index of array
        while left <= right
            while array[left] < pivot
                left := left + 1
            while array[right] > pivot
                right := right - 1
            if left <= right
                swap array[left] with array[right]
                left := left + 1
                right := right - 1
        quicksort(array from first index to right)
        quicksort(array from left to last index)

修改版:http://rosettacode.org/wiki/Sorting_algorithms/Quicksort

【讨论】:

谢谢。这确实是部分原因。最初,数字的范围是 0-9999。对RAND_MAX 范围内的数字进行排序会使事情变得更好,即它提高了快速排序变坏的限制。

以上是关于大桌子的快速排序出奇地慢的主要内容,如果未能解决你的问题,请参考以下文章

8大排序算法---我熟知3(归并排序/快速排序/堆排序)

快速排序到底有多快?(含代码分析9大排序算法并行运行对比视频)

程序员必知的8大排序-------冒泡排序,快速排序(java实现)

小白初识 - 快速排序(QuickSort)

图解程序员必须掌握的Java常用8大排序算法

JavaScript 实现 归并排序快速排序