4、线程关联的内存池
每每想到单线程下内存池飞一般的速度和多线程下蜗牛一般的速度我就不能原谅自己,为什么差这么多,就不能让多线程下内存分配更快一点吗?解决方法有了,那就是让缓存线程化,各个线程有自己私有的缓存,分配的时候预先从当前线程私有缓存分配,分配空了的时候去全局free表取一组freeunit或直接向系统申请一大块缓存(各个线程缓存完全独立),不管具体采用什么方式,速度都大幅度的提高了,虽然还是比单线程下内存池慢了许多,不过比前面提到的多线程内存池以及nedmalloc都要快很多,我的实现大概比nedmalloc快1.6 ~ 2倍,离单线程下内存池速度也很近了,只是多了些查找线程id比较线程id等动作而已,基本上达到了自己的目标。
看看第一版线程关联内存池的一些代码:
struct tm_bufunit
{
tm_pool *pool; //pool指针
union
{
tm_bufunit *next; //下一个块指针
char data[4]; //数据区域
};
};
struct tm_gcontrol
{
tm_bufunit *gfree;
CRITICAL_SECTION gcs;
tm_gcontrol() : gfree(NULL) { InitializeCriticalSection(&gcs); }
~tm_gcontrol() { DeleteCriticalSection(&gcs); }
Inline void lock() { EnterCriticalSection(&gcs); }
Inline void unlock() { LeaveCriticalSection(&gcs); }
void free(tm_bufunit *buf)
{
lock();
buf->next = gfree;
gfree = buf;
unlock();
}
};
struct tm_memblock
{
tm_memblock *next;
};
class tm_pool
{
private:
size_t bksize; //一个分配块大小
size_t onebknum; //一次分配多少个bksize
DWORD thid; //线程id
tm_bufunit *next; //pool中自由块链
tm_memblock *mbk; //trunk表
tm_gcontrol gcontrol; //全局free表
friend tm_poolset;
private:
void expand();
public:
tm_pool(size_t size, size_t bknum);
~tm_pool();
void destroy();
void *newobj();
static void delobj(void *pbuf);
};
class tm_poolset
{
public:
tm_poolset();
virtual ~tm_poolset();
//添加分配池
bool addpool(size_t size, size_t allocnum);
void *newobj(size_t size, size_t *osize=NULL);
void delobj(void *pbuf, size_t size);
void destroy();
tm_pool *findpool(size_t size)
{
TMPOOLS::iterator it = tmpools.lower_bound(size);
if(it != tmpools.end())
return it->second;
return NULL;
}
protected:
typedef std::map<size_t, tm_pool *> TMPOOLS;
TMPOOLS tmpools;
};
//公开的数据及函数
extern DWORD tm_tlsindex; //tls索引
//app初始化,分配index
void tm_init();
void tm_free();
//关联到该线程
void tm_attach();
void tm_detach();
tm_poolset *tm_getpoolset();
//添加trunk
bool tm_addtrunk(size_t size, size_t allocnum);
//tls相关分配
void *tm_new(size_t size, size_t *osize=NULL);
//tls相关释放
void tm_del(void *buf, size_t size);
.cpp代码如下:
tm_pool::tm_pool(size_t size, size_t bknum) :
next(NULL), mbk(NULL),
bksize(size), onebknum(bknum)
{
thid = GetCurrentThreadId();
}
tm_pool::~tm_pool()
{
destroy();
}
void tm_pool::destroy()
{
for(tm_memblock *p = mbk; p; )
{
tm_memblock *q = p->next;
free((char *)p);
p = q;
}
mbk = NULL;
next = NULL;
}
void *tm_pool::newobj()
{
if(! next)
{
gcontrol.lock();
if(gcontrol.gfree)
{
next = gcontrol.gfree;
gcontrol.gfree = NULL;
}
gcontrol.unlock();
}
if(! next)
{
expand();
}
tm_bufunit *head = next;
next = head->next;
// return (void *)head;
return (void *)head->data;
}
void tm_pool::delobj(void *pbuf)
{
// tm_bufunit *head = (tm_bufunit*)(pbuf);
tm_bufunit *head = (tm_bufunit *)((char *)pbuf-offsetof(tm_bufunit, data));
tm_pool *pool = head->pool;
if(pool->thid == GetCurrentThreadId())
{
head->next = pool->next;
pool->next = head;
}
else
{
pool->gcontrol.free(head);
}
}
void tm_pool::expand()
{
size_t unitsize = offsetof(tm_bufunit, data) + bksize;
size_t size = (unitsize * onebknum + sizeof(tm_memblock));
tm_memblock *pbk = (tm_memblock *)malloc(size);
pbk->next = mbk;
mbk = pbk;
tm_bufunit *p = (tm_bufunit*)((char *)pbk+sizeof(tm_memblock));
p->pool = this;
next = p;
for(size_t i=0; i<onebknum-1; ++i)
{
p->next = (tm_bufunit *)((char *)p+unitsize);
p = p->next;
p->pool = this;
}
p->next = NULL;
}
…
这一版基本实现了第一步的提速目标,并且每个分配块还记录了来自哪个pool,这样free的时候就省去了查找pool的动作,只是还有一些问题,如何判断一个内存是来源于malloc的分配还是来源于pool的分配没有做终结的判断,而且还留下了一个bug,对于a线程来说,可能只有256,512两个块的缓存,b线程可能多一个块1024,这样a线程分配的1024字节的内存是用malloc分配,到b线程释放的时候会调用pool释放,这个bug将在下一章解决。