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將在下一章解決。