池式对象分配

有了tcmalloc和jemalloc,在大多数情况下我们都没有必要再自己写通用的内存分配器(应该说对于极大多数的程序员,都不可能写出比这

个两个更好的通用内存分配器)。但是,如果对性能有极致的要求,写一个比通用内存分配器效率更高的池式对象分配器是可能的。

一个最简单也高效的实现就是freelist,每次分配的时候从freelist中get一个,释放的时候put回去就好了。其实现在单线程下是相当简单的,

也就几十行代码。但是在多线程的环境下,问题稍微复杂一点,因为可能有多个线程需要操作freelist,那么就要用锁去保护这个freelist,每次

get和put的时候都要加锁显然会导致freelist的操作效率低下.

我在 block_obj_allocator 中利使用了线程本地存储来减

少锁的使用,其核心思想是,每个线程都有一个本地的freelist,get和put操作的都是本地的freelist,当本地freelist为空时,向一个全局的freelist

,请求一大块的内存,这个时候需要锁操作。在执行put的时候,如果收集了一定数量的对象,就一次性将一定数量的对象返还给全局freelist,

这个时候也需要锁操作。下面提出另外一种实现方式,这个实现要更简单,其核心思想就是对象由谁分配就由谁负责释放。

    struct obj_block
    {
        struct dnode node;
        struct llist freelist;
        lockfree_stack_t que;
        pthread_t       thdid;//·ÖÅäÏ̵߳Äid
        char   buf[0];
    };
    
    struct obj_slot
    {
        struct lnode node;
        struct obj_block *block;
        char buf[0];
    };

obj_block是一个内存块管理器,当对象池中没有对象时,就分配一个obj_block,然后将buf中的内存分成一个个单独的obj_slot

添加到obj_block的freelist中.obj_block中记录了分配线程的线程id,保存在thdid变量中.

    struct pth_allocator
    {
        lockfree_stack que;
        uint32_t free_block_size;
        struct dlist free_blocks;
        struct dlist recy_blocks;
    };
    
    
    struct obj_allocator{
        struct allocator base;
        uint32_t alloc_size;
        uint32_t objsize;
        uint16_t tls_type;
        pthread_key_t pkey;
    };

pth_allocator是一个每线程的管理结构,free_block_size记录了当前管理结构可供分配的obj_block的数量,所有可供分配的obj_block

被添加到free_blocks中,而recy_blocks中保存了已经没有可分配对象的obj_block.分配过程是从free_blocks的表头中获得一个obj_block,

然后从obj_block的freelist中弹出一个空闲的对象,分配出去. 如果分配之后obj_block的freelist为空,则将obj_block从free_blocks中弹

出,添加到recy_blocks中.

这个设计的关键在que,它是一个无锁算法实现的栈,这里之所以使用栈,首先,我们不需要关注释放的顺序,其次无锁栈的实现最简单,

开销最小.

如果释放线程与分配线程不是同一个线程,则将要释放的对象push到que中,由分配线程在以后的某个时间里从que中取出,然后

放回到free_list中,释放部分的代码如下:

 

    void obj_dealloc(struct allocator *allo ,void *ptr)
    {
        obj_allocator_t _allo = (obj_allocator_t)allo;
        struct obj_slot *obj = (struct obj_slot*)((char*)ptr - sizeof(struct obj_slot));    
        if(obj->block->thdid == pthread_self()){

            struct pth_allocator *pth = (struct pth_allocator*)pthread_getspecific(_allo->pkey);;
            if(unlikely(!pth))
                abort();
            __dealloc(_allo,pth,obj);
        }
        else
            lfstack_push(obj->block->que,(struct lnode*)obj);
    }

再来看下分配的实现:

    void* obj_alloc(struct allocator *allo,int32_t size)
    {
        obj_allocator_t _allo = (obj_allocator_t)allo;
        struct pth_allocator *pth = (struct pth_allocator*)pthread_getspecific(_allo->pkey);
        if(unlikely(!pth))
        {
            pth = new_pth(_allo);
            pthread_setspecific(_allo->pkey,pth);
        }
        if(unlikely(dlist_empty(&pth->free_blocks)))
        {
            struct lnode *n;
            while((n = lfstack_pop(&pth->que)) != NULL)
                __dealloc(_allo,pth,(struct obj_slot*)n);            
        }else
            return __alloc(_allo,pth);
        if(unlikely(dlist_empty(&pth->free_blocks)))
            __expand(_allo,pth);
        return __alloc(_allo,pth);
    }

首先看下本地是否有可供分配的对象,如果有直接调用__alloc分配,如果本地没有则看下que里有没有其线程释放的对,如果有将这些对象回收到本地
的free_list中,接着再次查看本地是否有可供分配的对象,如果还是没有调用__expand分配一块内存,创建新的对象,然后再调用__alloc分配.


分配器的完整代码, 使用方式参看asynserver

 

 

posted @ 2014-03-01 23:03  sniperHW  阅读(1178)  评论(1编辑  收藏  举报