快速排序 Gnu glibc qsort_r
2012-10-22 19:00 respawn 阅读(4051) 评论(6) 编辑 收藏 举报
今天,又重新看了一下GLIBC中快速排序的源码~ 又多了一些不同的理解,为什么这篇文章迟迟没有
发布,也是因为对于源码没有深刻的理解,感觉很多点都不明白.今天就找了些资料,仔细揣摩了一遍源
码,索性就写出来,有不到之处,请批评指正~
之前一直都是以为GLIBC中的源码应该会花费大量的代码做通用处理,但是今天重新浏览之后,发现自己
错了,哎~ 不过还是蛮高兴的,毕竟还年轻嘛~
其实,GLIBC中快速排序的源码重点在榨取CPU的性能,提高快排的执行效率.为什么说是榨取呢? 因为
源码真的将利用效率做到了极致.
1 /* For large object sizes use indirect sorting. */ 2 if (s > 32) 3 size = 2 * n * sizeof (void *) + s; // 这里为什么要计算2N+1的空间?
看到前面的英文注释了吧,源码中对于快速排序也做了不同的分类,采用不同的方式.意思就是:如果单个元素
长度大于32BYTE,那么就视为大对象,采用间接排序.至于间接排序是什么意思?下面再说.
不知道有没有同学会不理解为什么会需要sizeof(void *).这个问题嘛,只能说脑袋总是有闹别扭的时候.
浪费一点时间回头看看qsort_r函数声明.
1 void 2 qsort_r (void *b, size_t n, size_t s, __compar_d_fn_t cmp, void *arg)
可以很清楚的看到,将目标数组参数的类型声明为void *,所以这里是需要计算需要的存储空间大小.然后联系
源码中采用的两种排序方式,间接快速排序和直接快速排序,如果只是单凭元素大小来选择排序方式,那是不可靠
的,为什么? 因为即使元素占用空间很大,但是数量很少还是不能采用间接排序,所以判断需要排序的目标数组的
长度才是最可靠地依据.(修正,其实这里指的是指针的长度,看没看出错误呢?广大同学们. -,-)
1 if (size < 1024) 2 /* The temporary array is small, so put it on the stack. */ 3 p.t = __alloca (size);
所以呢,上面的代码就可以说明一切了.
__alloca是什么?我们看看哈~ 它是一个宏:
1 #ifdef __GNUC__ 2 # define __alloca(size) __builtin_alloca (size) 3 #endif /* GCC. */
这是GCC特有的一个函数实现,查阅了很多的资料.手册中说_builtin_alloca是一个私有的实现版本.
其他的并没有多作说明. 其他的资料暂时也没有找到,知道的可以告诉我哈~
这几段代码告诉我们,若目标数组长度小于1024BYTE,那么就使用直接快速排序,否则就是用间接快速排序.
1 if (size / pagesize > (size_t) phys_pages) 2 { 3 _quicksort (b, n, s, cmp, arg); 4 return; 5 }
先跳过前面的几段代码,直接看看上面的这段代码,这里调用了直接快速排序,我们先不关心直接排序的实现,
先看看为什么会调用直接排序.注意一下条件. 需要弄懂这个条件,就需要看前面一段忽略的代码.
1 /* We should avoid allocating too much memory since this might 2 have to be backed up by swap space. */ 3 static long int phys_pages; 4 static int pagesize; 5 6 if (pagesize == 0) 7 { 8 phys_pages = __sysconf (_SC_PHYS_PAGES); 9 10 if (phys_pages == -1) 11 /* Error while determining the memory size. So let's 12 assume there is enough memory. Otherwise the 13 implementer should provide a complete implementation of 14 the `sysconf' function. */ 15 phys_pages = (long int) (~0ul >> 1); 16 17 /* The following determines that we will never use more than 18 a quarter of the physical memory. */ 19 phys_pages /= 4; 20 21 /* Make sure phys_pages is written to memory. */ 22 atomic_write_barrier (); 23 24 pagesize = __sysconf (_SC_PAGESIZE)
这段代码涉及到的函数比较的复杂,在本文中将不做详细解释,简单解释一下.这段代码是获取系统的
物理可用内存参数. 最主要的是避免使用交换空间,因为那样做会降低排序的效率.
简单解释了一下这段代码,前面的条件也就清晰明了了,如果系统的可用内存够支配,那么就从堆分配
内存采用间接快速排序.如果不够用了,那么就只好直接快速排序了.也就是直接调用_quicksort函数.
1 int save = errno; 2 tmp = malloc (size); 3 __set_errno (save); 4 if (tmp == NULL) 5 { 6 /* Couldn't get space, so use the slower algorithm 7 that doesn't need a temporary array. */ 8 _quicksort (b, n, s, cmp, arg); 9 return; 10 } 11 p.t = tmp;
使用malloc函数从堆分配所需内存空间,如果无法分配(无法分配的原因很多),那么依然采用间接快速排序.
如果分配了内存,那么将空间地址指针放入一个结构体中.看看这个结构体:
1 struct msort_param 2 { 3 size_t s; 4 size_t var; 5 __compar_d_fn_t cmp; 6 void *arg; 7 char *t; 8 };
1 p.s = s; 2 p.var = 4; 3 p.cmp = cmp; 4 p.arg = arg;
下面这段初始化的源码,初始化p中的成员. 对照着前面的结构体声明,各个参数的类型.
1 if (s > 32) // 如果单个元素长度>32 2 { 3 /* Indirect sorting. */ 4 char *ip = (char *) b; // 获取目标数组的地址. 5 void **tp = (void **) (p.t + n * sizeof (void *)); // 获取排序空间第N+1位置指针. 6 void **t = tp; 7 void *tmp_storage = (void *) (tp + n); // 排序空间的最后一个位置,申请的空间大小是2N+1.
8 // 注意这里为什么要使用void *,因为间接排序是对指针进行排序,而不是针对元素. 9 while ((void *) t < tmp_storage) 10 { 11 *t++ = ip; // 拷贝指针. 12 ip += s; // 移动到下一个指针. 因为类型是字节,所以要按照元素长度进行移位. 13 } 14 p.s = sizeof (void *); 15 p.var = 3; 16 msort_with_tmp (&p, p.t + n * sizeof (void *), n); 17 18 /* tp[0] .. tp[n - 1] is now sorted, copy around entries of 19 the original array. Knuth vol. 3 (2nd ed.) exercise 5.2-10. */ 20 char *kp; 21 size_t i; 22 for (i = 0, ip = (char *) b; i < n; i++, ip += s) 23 if ((kp = tp[i]) != ip) 24 { 25 size_t j = i; 26 char *jp = ip; 27 memcpy (tmp_storage, ip, s); 28 29 do 30 { 31 size_t k = (kp - (char *) b) / s; 32 tp[j] = jp; 33 memcpy (jp, kp, s); 34 j = k; 35 jp = kp; 36 kp = tp[k]; 37 } 38 while (kp != ip); 39 40 tp[j] = jp; 41 memcpy (jp, tmp_storage, s); 42 } 43 } 44 else 45 {
// 下面的源码就没什么了,主要是依照条件初始化p.var 46 if ((s & (sizeof (uint32_t) - 1)) == 0 47 && ((char *) b - (char *) 0) % __alignof__ (uint32_t) == 0) 48 { 49 if (s == sizeof (uint32_t)) 50 p.var = 0; 51 else if (s == sizeof (uint64_t) 52 && ((char *) b - (char *) 0) % __alignof__ (uint64_t) == 0) 53 p.var = 1; 54 else if ((s & (sizeof (unsigned long) - 1)) == 0 55 && ((char *) b - (char *) 0) 56 % __alignof__ (unsigned long) == 0) 57 p.var = 2; 58 } 59 msort_with_tmp (&p, b, n); 60 } 61 free (tmp);
上面的注释中说明了,很大一段代码都是依照条件去初始化p中var的值,在源码中,可以看到,
p.var有四个值,分别是0,1,2,3. 分别代表什么意思呢? 要到msort_with_tmp函数的源码中
去看看才知道.同样,想要搞明白,间接快速排序时如何做的,也要进去看看.
下面的分析,留在下一篇文章中再续吧,有事处理一下~