您的位置:首页 > 其它

快速排序 Gnu glibc qsort_r

2012-10-22 19:00 162 查看
今天,又重新看了一下GLIBC中快速排序的源码~ 又多了一些不同的理解,为什么这篇文章迟迟没有

发布,也是因为对于源码没有深刻的理解,感觉很多点都不明白.今天就找了些资料,仔细揣摩了一遍源

码,索性就写出来,有不到之处,请批评指正~

之前一直都是以为GLIBC中的源码应该会花费大量的代码做通用处理,但是今天重新浏览之后,发现自己

错了,哎~ 不过还是蛮高兴的,毕竟还年轻嘛~

其实,GLIBC中快速排序的源码重点在榨取CPU的性能,提高快排的执行效率.为什么说是榨取呢? 因为

源码真的将利用效率做到了极致.

/* For large object sizes use indirect sorting.  */
if (s > 32)
size = 2 * n * sizeof (void *) + s; // 这里为什么要计算2N+1的空间?


看到前面的英文注释了吧,源码中对于快速排序也做了不同的分类,采用不同的方式.意思就是:如果单个元素

长度大于32BYTE,那么就视为大对象,采用间接排序.至于间接排序是什么意思?下面再说.

不知道有没有同学会不理解为什么会需要sizeof(void *).这个问题嘛,只能说脑袋总是有闹别扭的时候.

浪费一点时间回头看看qsort_r函数声明.

void
qsort_r (void *b, size_t n, size_t s, __compar_d_fn_t cmp, void *arg)


可以很清楚的看到,将目标数组参数的类型声明为void *,所以这里是需要计算需要的存储空间大小.然后联系

源码中采用的两种排序方式,间接快速排序和直接快速排序,如果只是单凭元素大小来选择排序方式,那是不可靠

的,为什么? 因为即使元素占用空间很大,但是数量很少还是不能采用间接排序,所以判断需要排序的目标数组的

长度才是最可靠地依据.(修正,其实这里指的是指针的长度,看没看出错误呢?广大同学们. -,-)

if (size < 1024)
/* The temporary array is small, so put it on the stack.  */
p.t = __alloca (size);


所以呢,上面的代码就可以说明一切了.

__alloca是什么?我们看看哈~ 它是一个宏:

#ifdef    __GNUC__
# define __alloca(size)    __builtin_alloca (size)
#endif /* GCC.  */


这是GCC特有的一个函数实现,查阅了很多的资料.手册中说_builtin_alloca是一个私有的实现版本.

其他的并没有多作说明. 其他的资料暂时也没有找到,知道的可以告诉我哈~

这几段代码告诉我们,若目标数组长度小于1024BYTE,那么就使用直接快速排序,否则就是用间接快速排序.

if (size / pagesize > (size_t) phys_pages)
{
_quicksort (b, n, s, cmp, arg);
return;
}


先跳过前面的几段代码,直接看看上面的这段代码,这里调用了直接快速排序,我们先不关心直接排序的实现,

先看看为什么会调用直接排序.注意一下条件. 需要弄懂这个条件,就需要看前面一段忽略的代码.

/* We should avoid allocating too much memory since this might
have to be backed up by swap space.  */
static long int phys_pages;
static int pagesize;

if (pagesize == 0)
{
phys_pages = __sysconf (_SC_PHYS_PAGES);

if (phys_pages == -1)
/* Error while determining the memory size.  So let's
assume there is enough memory.  Otherwise the
implementer should provide a complete implementation of
the `sysconf' function.  */
phys_pages = (long int) (~0ul >> 1);

/* The following determines that we will never use more than
a quarter of the physical memory.  */
phys_pages /= 4;

/* Make sure phys_pages is written to memory.  */
atomic_write_barrier ();

pagesize = __sysconf (_SC_PAGESIZE)


这段代码涉及到的函数比较的复杂,在本文中将不做详细解释,简单解释一下.这段代码是获取系统的

物理可用内存参数. 最主要的是避免使用交换空间,因为那样做会降低排序的效率.

简单解释了一下这段代码,前面的条件也就清晰明了了,如果系统的可用内存够支配,那么就从堆分配

内存采用间接快速排序.如果不够用了,那么就只好直接快速排序了.也就是直接调用_quicksort函数.

int save = errno;
tmp = malloc (size);
__set_errno (save);
if (tmp == NULL)
{
/* Couldn't get space, so use the slower algorithm
that doesn't need a temporary array.  */
_quicksort (b, n, s, cmp, arg);
return;
}
p.t = tmp;


使用malloc函数从堆分配所需内存空间,如果无法分配(无法分配的原因很多),那么依然采用间接快速排序.

如果分配了内存,那么将空间地址指针放入一个结构体中.看看这个结构体:

struct msort_param
{
size_t s;
size_t var;
__compar_d_fn_t cmp;
void *arg;
char *t;
};


p.s = s;
p.var = 4;
p.cmp = cmp;
p.arg = arg;


下面这段初始化的源码,初始化p中的成员. 对照着前面的结构体声明,各个参数的类型.

if (s > 32)  // 如果单个元素长度>32
{
/* Indirect sorting.  */
char *ip = (char *) b; // 获取目标数组的地址.
void **tp = (void **) (p.t + n * sizeof (void *)); // 获取排序空间第N+1位置指针.
void **t = tp;
void *tmp_storage = (void *) (tp + n); // 排序空间的最后一个位置,申请的空间大小是2N+1.

// 注意这里为什么要使用void *,因为间接排序是对指针进行排序,而不是针对元素.
while ((void *) t < tmp_storage)
{
*t++ = ip; // 拷贝指针.
ip += s;   // 移动到下一个指针. 因为类型是字节,所以要按照元素长度进行移位.
}
p.s = sizeof (void *);
p.var = 3;
msort_with_tmp (&p, p.t + n * sizeof (void *), n);

/* tp[0] .. tp[n - 1] is now sorted, copy around entries of
the original array.  Knuth vol. 3 (2nd ed.) exercise 5.2-10.  */
char *kp;
size_t i;
for (i = 0, ip = (char *) b; i < n; i++, ip += s)
if ((kp = tp[i]) != ip)
{
size_t j = i;
char *jp = ip;
memcpy (tmp_storage, ip, s);

do
{
size_t k = (kp - (char *) b) / s;
tp[j] = jp;
memcpy (jp, kp, s);
j = k;
jp = kp;
kp = tp[k];
}
while (kp != ip);

tp[j] = jp;
memcpy (jp, tmp_storage, s);
}
}
else
{
// 下面的源码就没什么了,主要是依照条件初始化p.var
if ((s & (sizeof (uint32_t) - 1)) == 0
&& ((char *) b - (char *) 0) % __alignof__ (uint32_t) == 0)
{
if (s == sizeof (uint32_t))
p.var = 0;
else if (s == sizeof (uint64_t)
&& ((char *) b - (char *) 0) % __alignof__ (uint64_t) == 0)
p.var = 1;
else if ((s & (sizeof (unsigned long) - 1)) == 0
&& ((char *) b - (char *) 0)
% __alignof__ (unsigned long) == 0)
p.var = 2;
}
msort_with_tmp (&p, b, n);
}
free (tmp);


上面的注释中说明了,很大一段代码都是依照条件去初始化p中var的值,在源码中,可以看到,

p.var有四个值,分别是0,1,2,3. 分别代表什么意思呢? 要到msort_with_tmp函数的源码中

去看看才知道.同样,想要搞明白,间接快速排序时如何做的,也要进去看看.

下面的分析,留在下一篇文章中再续吧,有事处理一下~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: