现在的位置: 首页 > 综合 > 正文

深入分析qsort库函数(三)--测试结果

2018年02月11日 ⁄ 综合 ⁄ 共 5834字 ⁄ 字号 评论关闭
        网上的文章当提到std::sort和qsort的区别时,通常把它们的性能差异归因于qsort的反引用开销,我们在这里通过实验来测试看看是不是这样,并且判断std::sort的算法是否有较之于qsort代码更优的性能,或者反过来。
        测试用的源代码如下:
main.cpp 主函数所在的文件
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <algorithm>
#include "m_qsort.cpp"
#include "funcs.h"
using namespace std;
#define N 1000000
int comp(const void* a,const void* b)
{
 return *(int*)a-*(int*)b;
}
void main()
{
 int i,j,*a,*b;
 clock_t st,et,tt[3]={0};
 a=(int*)malloc(sizeof(int)*N);
 b=(int*)malloc(sizeof(int)*N);
 getchar();
 for (i=0;i<10;i++)
 {
  m_srand((long)time(NULL));
  for (j=0;j<N;j++) a[j]=m_rand();
 
  memcpy(b,a,N*sizeof(int));
  st=clock();
  qsort(b,N,sizeof(int),comp);
  et=clock();
  tt[0]+=(et-st);
 
  memcpy(b,a,N*sizeof(int));
  st=clock();
  m_qsort2(b,N);
  et=clock();
  tt[1]+=(et-st);

  memcpy(b,a,N*sizeof(int));
  st=clock();
  sort(b,b+N);
  et=clock();
  tt[2]+=(et-st);
 }
 printf("qsort %d ms/n",tt[0]/10);
 printf("m_qsort2 %d ms/n",tt[1]/10);
 printf("sort %d ms/n",tt[2]/10);
 getchar();
}
funcs.h
void m_srand(long seed);
int m_rand();
m_qsort.cpp  用于测试qsort修改版的代码
#include <algorithm>
using namespace std;
/* Always compile this module for speed, not size */
#pragma optimize("t", on)
/* prototypes for local routines */
template<class _RanIt>
static void __cdecl shortsort(_RanIt *lo, _RanIt *hi);
#define CUTOFF 8            /* testing shows that this is good value */
#define STKSIZ (8*sizeof(void*) - 2)
 
template<class _RanIt>
void m_qsort (
    _RanIt base,
    size_t num
    )
{
    _RanIt lo, hi;              /* ends of sub-array currently sorting */
    _RanIt mid;                  /* points to middle of subarray */
    _RanIt loguy, higuy;        /* traveling pointers for partition step */
    size_t size;                /* size of the sub-array */
    _RanIt lostk[STKSIZ], histk[STKSIZ];
    int stkptr;                 /* stack for saving sub-array to be processed */
    if (num < 2)
        return;                 /* nothing to do */
    stkptr = 0;                 /* initialize stack */
    lo = base;
    hi = base +num-1;        /* initialize limits */
recurse:
    size = hi - lo + 1;        /* number of el's to sort */
    /* below a certain size, it is faster to use a O(n^2) sorting method */
    if (size <= CUTOFF) {
        shortsort(lo, hi);
  //_Insertion_sort(lo,hi+1);
    }
    else {
        mid = lo + size / 2;      /* find middle element */
        /* Sort the first, middle, last elements into order */
        if (*lo>*mid) {
            iter_swap(lo, mid);
        }
        if (*lo>*hi) {
            iter_swap(lo, hi);
        }
        if (*mid>*hi) {
            iter_swap(mid, hi);
        }
        loguy = lo;
        higuy = hi;
        for (;;) {
            if (mid > loguy) {
                do  {
                    loguy ++;
                } while (loguy < mid && *loguy<=*mid);
            }
            if (mid <= loguy) {
                do  {
                    loguy ++;
                } while (loguy <= hi && *loguy<=*mid);
            }
            /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy,
               either loguy > hi or A[loguy] > A[mid] */
            do  {
                higuy --;
            } while (higuy > mid && *higuy> *mid);
            if (higuy < loguy)
                break;
            iter_swap(loguy, higuy);
            if (mid == higuy)
                mid = loguy;
        }
        higuy ++;
        if (mid < higuy) {
            do  {
                higuy --;
            } while (higuy > mid && *higuy==*mid);
        }
        if (mid >= higuy) {
            do  {
                higuy --;
            } while (higuy > lo && *higuy==*mid);
        }
        if ( higuy - lo >= hi - loguy ) {
            if (lo < higuy) {
                lostk[stkptr] = lo;
                histk[stkptr] = higuy;
                ++stkptr;
            }                           /* save big recursion for later */
            if (loguy < hi) {
                lo = loguy;
                goto recurse;           /* do small recursion */
            }
        }
        else {
            if (loguy < hi) {
                lostk[stkptr] = loguy;
                histk[stkptr] = hi;
                ++stkptr;               /* save big recursion for later */
            }
            if (lo < higuy) {
                hi = higuy;
                goto recurse;           /* do small recursion */
            }
        }
    }
    --stkptr;
    if (stkptr >= 0) {
        lo = lostk[stkptr];
        hi = histk[stkptr];
        goto recurse;           /* pop subarray from stack */
    }
    else
 {
        return;                 /* all subarrays done */
 }
}
template<class _RanIt>
static void __cdecl shortsort (
    _RanIt *lo,
    _RanIt *hi
    )
{
    _RanIt *p, *max;
    while (hi > lo) {
        /* A[i] <= A[j] for i <= j, j > hi */
        max = lo;
        for (p = lo+1; p <= hi; p ++) {
            /* A[i] <= A[max] for lo <= i < p */
            if (*p>*max) {
                max = p;
            }
        }
        iter_swap(max, hi);
        hi --;
    }
}
m_rand.cpp 用于生成0~0x00ffffff之间随机数的代码
static long holdrand;
void m_srand(long seed)
{
 holdrand=seed;
}
int m_rand()
{
 return ((holdrand = holdrand * 214013L + 2531011L) & 0x00ffffff);
}
       
        主函数分成三段测试代码,每段测试一个函数:qsort、m_qsort、std::sort。其中m_qsort是复制qsort的代码过来然后修改而成。测试数据共有10组,取平均值输出。下面我们开始测试。
        先测试qsort和std::sort两个函数,把另外两段测试代码注释掉以节约时间:
qsort 1156 ms
sort 860 ms
        实验证明,std::sort比qsort快25.6%。我们把N扩大为两倍2000000,即数组大小扩大成两倍,测试结果:
qsort 2416 ms
sort 1828 ms
        实验结果表明在数据增长成两倍的时候,qsort运行时间增长为原来的2.09倍;sort增长为原来的2.13倍。sort算法的增长速度稍快。
        std::sort比qsort快25.6%,到底快在哪里呢?我们现在来看看。我们修改了qsort,成为m_qsort,它使用了模板,不需要传入函数指针,而且换掉了原来低效的逐个字节交换的swap函数,用iter_swap代替。比较三个函数:
qsort 1131 ms
m_qsort 640 ms
sort 857 ms
        我们发现,经过对qsort函数进行修改之后,它竟然比sort函数快25.3%!这说明qsort函数的主要开销在于直接对字节指针的操作。这同时也说明,对于基本没有重复键的数据来说,qsort比sort要快。
        我们再来比较一下特殊情况。使用系统自带的srand函数和rand函数,生成0~0x00007fff的数,这样1000000个数中就会每个数平均有31个重复值。我们看一下运行结果:
qsort 894 ms
m_qsort 519 ms
sort 554 ms
        可以清楚地看到,在数据重复比较多的时候,sort的性能明显得到了提高。考虑一种极限情况,所有数据相同,我们修改代码再运行一次:
qsort 72 ms
m_qsort 42 ms
sort 24 ms
        这次很明显了,对于重复数据,sort函数的处理能力明显强于qsort。这主要是和sort函数三路划分分得更细致有关。
        我们接着考虑递增和递减数组。修改代码然后测试,结果如下:
qsort 497 ms
m_qsort 234 ms
sort 203 ms
        sort函数的运行时间比m_qsort要少。我们可以看到,相比随机数据,有序数据在快速排序的时候得到了很好的优化。再来看递减的数组。
qsort 534 ms
m_qsort 261 ms
sort 338 ms
        递减数组中sort函数略逊于qsort改进版,这大概是因为sort函数取样9个点造成过多交换开销造成的。
        从整体上看,我们得出这样一个结论:对于随机基本无重复的数据,qsort的改进版比sort函数优秀;而sort函数由于对分区比较细致,所以处理重复数据较多的数组则会比较优化。而我们平常所遇到的数据,比如出生年月,性别等,都有很多的重复值,所以sort函数就成为了排序这些数据的首选。

抱歉!评论已关闭.