有了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