快速排序 Gnu glibc qsort_r
2012-10-22 19:00
162 查看
今天,又重新看了一下GLIBC中快速排序的源码~ 又多了一些不同的理解,为什么这篇文章迟迟没有
发布,也是因为对于源码没有深刻的理解,感觉很多点都不明白.今天就找了些资料,仔细揣摩了一遍源
码,索性就写出来,有不到之处,请批评指正~
之前一直都是以为GLIBC中的源码应该会花费大量的代码做通用处理,但是今天重新浏览之后,发现自己
错了,哎~ 不过还是蛮高兴的,毕竟还年轻嘛~
其实,GLIBC中快速排序的源码重点在榨取CPU的性能,提高快排的执行效率.为什么说是榨取呢? 因为
源码真的将利用效率做到了极致.
看到前面的英文注释了吧,源码中对于快速排序也做了不同的分类,采用不同的方式.意思就是:如果单个元素
长度大于32BYTE,那么就视为大对象,采用间接排序.至于间接排序是什么意思?下面再说.
不知道有没有同学会不理解为什么会需要sizeof(void *).这个问题嘛,只能说脑袋总是有闹别扭的时候.
浪费一点时间回头看看qsort_r函数声明.
可以很清楚的看到,将目标数组参数的类型声明为void *,所以这里是需要计算需要的存储空间大小.然后联系
源码中采用的两种排序方式,间接快速排序和直接快速排序,如果只是单凭元素大小来选择排序方式,那是不可靠
的,为什么? 因为即使元素占用空间很大,但是数量很少还是不能采用间接排序,所以判断需要排序的目标数组的
长度才是最可靠地依据.(修正,其实这里指的是指针的长度,看没看出错误呢?广大同学们. -,-)
所以呢,上面的代码就可以说明一切了.
__alloca是什么?我们看看哈~ 它是一个宏:
这是GCC特有的一个函数实现,查阅了很多的资料.手册中说_builtin_alloca是一个私有的实现版本.
其他的并没有多作说明. 其他的资料暂时也没有找到,知道的可以告诉我哈~
这几段代码告诉我们,若目标数组长度小于1024BYTE,那么就使用直接快速排序,否则就是用间接快速排序.
先跳过前面的几段代码,直接看看上面的这段代码,这里调用了直接快速排序,我们先不关心直接排序的实现,
先看看为什么会调用直接排序.注意一下条件. 需要弄懂这个条件,就需要看前面一段忽略的代码.
这段代码涉及到的函数比较的复杂,在本文中将不做详细解释,简单解释一下.这段代码是获取系统的
物理可用内存参数. 最主要的是避免使用交换空间,因为那样做会降低排序的效率.
简单解释了一下这段代码,前面的条件也就清晰明了了,如果系统的可用内存够支配,那么就从堆分配
内存采用间接快速排序.如果不够用了,那么就只好直接快速排序了.也就是直接调用_quicksort函数.
使用malloc函数从堆分配所需内存空间,如果无法分配(无法分配的原因很多),那么依然采用间接快速排序.
如果分配了内存,那么将空间地址指针放入一个结构体中.看看这个结构体:
下面这段初始化的源码,初始化p中的成员. 对照着前面的结构体声明,各个参数的类型.
上面的注释中说明了,很大一段代码都是依照条件去初始化p中var的值,在源码中,可以看到,
p.var有四个值,分别是0,1,2,3. 分别代表什么意思呢? 要到msort_with_tmp函数的源码中
去看看才知道.同样,想要搞明白,间接快速排序时如何做的,也要进去看看.
下面的分析,留在下一篇文章中再续吧,有事处理一下~
发布,也是因为对于源码没有深刻的理解,感觉很多点都不明白.今天就找了些资料,仔细揣摩了一遍源
码,索性就写出来,有不到之处,请批评指正~
之前一直都是以为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函数的源码中
去看看才知道.同样,想要搞明白,间接快速排序时如何做的,也要进去看看.
下面的分析,留在下一篇文章中再续吧,有事处理一下~
相关文章推荐
- 快速排序 Gnu glibc qsort
- C语言中用qsort()快速排序
- c语言字符串快速排序qsort()
- 快速排序 qsort
- 快速排序 qsort
- 快速排序,void Qsort(void *base,int nelem,int width, int (*fcmp)(const void *,const void *))的实现。
- 快速排序qsort和sort的用法
- 快速排序的库函数qsort与sort的使用方法
- 快速排序(qsort)
- 在C语言的库函数中就有快速排序的库函数,即为qsort
- 问题 C: 快速排序 qsort [2*]
- 快速排序与折半查找算法函数:qsort与bsearch
- 快速排序 C语言的qsort及C++的sort
- 调用库函数进行的qsort快速排序
- C/C++ qsort()快速排序的用法
- qsort 快速排序
- C/C++ qsort()快速排序用法
- 快速排序 qsort()函数
- C/C++ 库函数快速排序 qsort
- C语言快速排序函数qsort