Chinaunix首页 | 论坛 | 博客
  • 博客访问: 26390
  • 博文数量: 5
  • 博客积分: 90
  • 博客等级: 民兵
  • 技术积分: 45
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-30 10:57
文章分类

全部博文(5)

文章存档

2011年(5)

我的朋友
最近访客

分类: C/C++

2011-06-07 22:33:27

    每次在设计新的网络服务器时,都会愁于反复为各种对象实现内存池,特别是最常用的链表、哈希表、IO缓存块,总无法平衡扩展性、性能、内存占用,红黑树或定长块都不顶用。最近再次研究apache及nginx的池设计,一下子好像想通了。
    无论链表、哈希表、IO缓存块,实现起来都不外是增删改查,效率瓶颈都是在malloc调用以及定位目标块的速度,只要能减少结点规模增长下的malloc次数,再能用位操作来直接定位,一定会很块。
    考虑用阶梯式的二维数组来存储块,阶级之间两倍块数增长,并用同样的阶梯数组来存储空闲指针,这样即使基数用1024字节,在第20阶也能存储2^29个结点了,总2^30-1约10亿结点,总共malloc才20次解决了内存申请的瓶颈;
    在阶梯数组里,定位第n个块,需要找出n作为二进制时最高为1的位置x,它就是梯级号。然后n减去前x项和,就是它所在梯级里的块偏移y.这都好实现。
    后面附上实现,在本人的T42上测试,用它来实现双向链表,1千万条int记录插入约用时600毫秒,占用内存约180M,遍历并间断删除2百万条约用时200毫秒,析构约用时200毫秒。而stl的list,上述几个数据分别约为1700ms,235M, 600ms,1800ms.用来实现hash表时,其插入差距更大,基本上够用了。
  1. #ifndef _BPOOL_H_
  2. #define _BPOOL_H_

  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <malloc.h>
  6. #include <assert.h>

  7. #ifdef __cplusplus
  8. extern "C" {
  9. #endif

  10. #ifdef _MSC_VER
  11. #define inline _inline
  12. #endif

  13. #define SIZE_AUTO_EXPAND 0 ///自由增长
  14. #define ARRAY_ALLOC_BASE 10 ///表示第一阶梯数组的块数是1024
  15. #define MAX_ARRAY_INDEX 18 ///最大阶梯数

  16. typedef struct bpool_t {
  17.     int maxcount; //最大块数支持
  18.     int nblock; //使用中的块数
  19.     int nfreeblock; //空闲块数
  20.     int nallblock; //已申请块数
  21.     int nindex; //已使用到的梯级号
  22.     int bsize; //每块字节数
  23.     void **freeits[MAX_ARRAY_INDEX]; //空闲指针阶梯数组
  24.     void *allits[MAX_ARRAY_INDEX]; //块所在的阶梯数组
  25. } bpool_t;

  26. static inline void bpool_init (bpool_t *bp, int maxcount, int bsize)
  27. {
  28.     assert (bp);
  29.     memset (bp, 0, sizeof(bpool_t));
  30.     bp->maxcount = maxcount;
  31.     bp->bsize = bsize;
  32. }

  33. static inline void bpool_clear (bpool_t *bp)
  34. {
  35.     int i;
  36.     assert (bp);
  37.     for (i=0; i<bp->nindex; i++) {
  38.         //if (bp->allits[i]) free (bp->allits[i]);
  39.         if (bp->freeits[i]) free ((void*)bp->freeits[i]);    
  40.     }
  41. }

  42. //计算32位前导0数,抄的
  43. static inline int nlz(unsigned x)
  44. {
  45.    int n = 1;
  46.    if (x == 0) return(32);
  47.    if ((x >> 16) == 0) {n = n +16; x = x <<16;}
  48.    if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
  49.    if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
  50.    if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
  51.    n = n - (x >> 31);
  52.    return n;
  53. }

  54. // 1 2 4 8 16...2^19
  55. static inline void *bpool_locat_block (void *head[], int offset, int isize)
  56. {
  57.     //第几个索引数组,先除以基数,再取最高为1的位的位置
  58.     //前加1是保证为0或全1时进位,后减1是为了从0开始取索引
  59.     //对于freeits和allits阶梯数组,第i个数组有base*(i^2)个项
  60.     //则对于第n个,其梯级号是
  61.     int x = 32 - nlz((offset>>ARRAY_ALLOC_BASE) + 1) - 1;
  62.     //数组内偏移, 减去前x个之和
  63.     int y = offset - (((1<<x)-1) << ARRAY_ALLOC_BASE); //前x个之和
  64.     char *arr = head[x];
  65.     assert (x < MAX_ARRAY_INDEX);
  66.     assert (y < ((1<<x)<<ARRAY_ALLOC_BASE));
  67.     return (void*)(arr+y*isize);
  68. }

  69. static inline void *bpool_alloc_block(bpool_t *bp)
  70. {
  71.     int len = 0;
  72.     int ncount = 0;
  73.     void *node, **pret=NULL;
  74.     char *mem = NULL;
  75.     
  76.     if (bp->nfreeblock > 0)
  77.      goto __found;
  78.     
  79.     ncount = (1<<bp->nindex)<<ARRAY_ALLOC_BASE;
  80.     if (bp->maxcount!=SIZE_AUTO_EXPAND && ncount > bp->maxcount - bp->nallblock)
  81.         ncount = bp->maxcount-bp->nallblock;
  82.         
  83.     if (ncount<=0 || bp->nindex == MAX_ARRAY_INDEX)
  84.         return NULL;//到限制了
  85.         
  86.     //新增加数组
  87.     mem = (char*)malloc ((sizeof(void*) + bp->bsize) * ncount);
  88.     if (!mem)
  89.         return NULL;

  90.     len = sizeof(void*)*ncount;
  91.     bp->freeits[bp->nindex] = (void**)mem;
  92.     bp->allits[bp->nindex] = (void*)(mem+len);

  93.     //只需要初始化freeits
  94.     memset (bp->freeits[bp->nindex], 0, len);
  95.     bp->nindex++;
  96.     
  97.     //freeits里有itemcount个未确定其真实空闲void内存的位置.为NULL
  98.     //allits里有itemcount个未作任何挂接的位置
  99.     //除了第一个要返回使用,其它空闲要加到free里
  100.     //每次申请时再动态挂接上
  101.     bp->nallblock += ncount;
  102.     bp->nfreeblock += ncount;
  103.     
  104. __found:
  105.     //目标是取me->nfreeblock--的位置
  106.     bp->nfreeblock--;
  107.     pret = (void**)bpool_locat_block ((void**)bp->freeits, bp->nfreeblock, sizeof(void*));
  108.     if (!*pret)
  109.         //原先未放有void内存,这个时候再挂接上去
  110.         //挂谁上去?此时满足count+ not_mount =nallblock
  111.         //例如第3次时,cnt=3,应挂第3个
  112.         //如果归还过,则直到用完以前归还的item,才会进本分支
  113.         *pret = (void*)bpool_locat_block ((void**)bp->allits, bp->nblock, bp->bsize);

  114.     bp->nblock++;
  115.     //拿走后要设置这个位置为空,否则若再扩展就会污染
  116.     node = *pret;
  117.     *pret = NULL;
  118.     return node;
  119. }

  120. static inline void bpool_free_block (bpool_t *bp, void *p)
  121. {
  122.     //直接将此void*挂接到freeits的最后可用结点上
  123.     void **pret = (void**)bpool_locat_block ((void**)bp->freeits, bp->nfreeblock, sizeof(void*));
  124.     assert (pret && bp->nfreeblock < bp->nallblock);
  125.     *pret = p;
  126.     bp->nblock--;
  127.     bp->nfreeblock++;
  128. }

  129. #ifdef __cplusplus
  130. }
  131. #endif

  132. #endif //_BPOOL_H_
阅读(2124) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:objdump 简单使用

给主人留下些什么吧!~~