使用對(duì)象池加速游戲內(nèi)存分配
游戲開(kāi)發(fā)中經(jīng)常需要頻繁產(chǎn)生、銷(xiāo)毀大量對(duì)象,內(nèi)存本身夠不夠用是一方面,尤其是在手機(jī)等內(nèi)存本來(lái)就有限的設(shè)備上面,另外一點(diǎn)是分配的速度不會(huì)對(duì)游戲體驗(yàn)造成影響,也就是不能影響幀率。
相比內(nèi)存池,對(duì)象池更易用更容易管理,而且還可以利用臟數(shù)據(jù),也就是上次被回收掉的對(duì)象的數(shù)據(jù)。而且偶爾的空間分配失敗其實(shí)不是那么重要(后面會(huì)講怎么在會(huì)失敗的情況下完成分配任務(wù)),游戲中還是速度更重要些。
原理
一次申請(qǐng)大量連續(xù)內(nèi)存(整數(shù)個(gè)對(duì)象大小),最好用堆,當(dāng)然如果用棧數(shù)組也沒(méi)人攔你,棧空間可是相當(dāng)有限…
由于分配的對(duì)象生存期是不固定的(如下圖),池不可能保持已分配對(duì)象的連續(xù)性,這時(shí)進(jìn)行塊移動(dòng)會(huì)降低程序效率。
分配 | 分配 | 分配 | 分配 |
所以需要把閑置對(duì)象的指針?lè)湃肴萜髦衼?lái)管理。此容器必須能快速存取刪,而且不需要頻繁大距離移動(dòng)容器元素指針,最好是剛從容器中釋放的元素能馬上讓 下一個(gè)元素使用,這時(shí)候棧就是一個(gè)很好的選擇了。初始時(shí)將所有閑置對(duì)象指針壓入棧,分配時(shí)pop,棧為空時(shí)返回空;釋放時(shí)將對(duì)象指針push進(jìn)棧即可。
實(shí)現(xiàn)
其實(shí)boost已經(jīng)提供了對(duì)象池了,那為什么還要自己實(shí)現(xiàn)一個(gè)呢?當(dāng)然是要方便DIY了…其實(shí)你也可以用boost的對(duì)象池來(lái)第二次封裝
這部分直接參看附件源碼吧
使用
這才是真正的重點(diǎn)
分配時(shí)直接用Sobot* p = ObjPool<Sobot>::alloc()?不,還應(yīng)該使用placement new調(diào)用其構(gòu)造函數(shù):
new(p) Sobot()
你想在你的代碼中充斥大量這樣的代碼嗎?放到工廠里面也許是一種辦法,但是工廠引用到了對(duì)象池了。而大師告訴我們好的設(shè)計(jì)要保持職責(zé)單一,用與不用對(duì)象池應(yīng)該不影響原系統(tǒng)的正常運(yùn)行。而且還有一點(diǎn),用這種辦法,就只能和某些組件絕緣了,比如智能指針。
此時(shí)重載new與delete就至關(guān)重要了:
- static void* operator new(size_t) {
- return SobotPool::instance().alloc();
- }
- static void operator delete(void* p) {
- SobotPool::instance().free(reinterpret_cast<Sobot*>(p));
- }
一個(gè)對(duì)象中往往充斥著大量指針,而這些指針指向的空間往往大于包含他們的對(duì)象本身。如果將這些指針?biāo)谠陬?lèi)也應(yīng)用對(duì)象池,一方面是池的容量你無(wú)法估 計(jì),另一方面是使用起來(lái)麻煩。而且你也無(wú)法向上面這樣給每個(gè)類(lèi)注入new與delete的重載。用代理?呵呵,項(xiàng)目中估計(jì)會(huì)出一堆問(wèn)題。這時(shí)候我們不妨使 用臟數(shù)據(jù),也就是說(shuō)對(duì)象池中保存的對(duì)象全是可以直接使用的對(duì)象,而并非空對(duì)象,對(duì)象中的成員指針變量引用到的內(nèi)存不在池中。為了保證安全,清空這些內(nèi)存在 池銷(xiāo)毀時(shí)進(jìn)行。
和上面的功能一起,我們可以定義一個(gè)宏,免得每次使用都得重復(fù)大量代碼。如下:
- #define USING_DIRTY_DATA true
- // 如果不是方便測(cè)試需要,可以將這行
- // typedef ObjPool<obj_class, max_size> obj_class##Pool; \
- // 標(biāo)注為private
- #define DECLARE_USING_OBJ_POOL(obj_class, max_size, _using_dirty_data) \
- public: \
- typedef ObjPool<obj_class, max_size> obj_class##Pool; \
- friend class obj_class##Pool; \
- static const bool using_dirty_data = _using_dirty_data; \
- public: \
- ~obj_class() { \
- if (!_using_dirty_data) {this->purge();} \
- } \
- static void* operator new(size_t) { \
- return obj_class##Pool::instance().alloc(); \
- } \
- static void operator delete(void* p) { \
- obj_class##Pool::instance().free(reinterpret_cast<obj_class*>(p)); \
- } \
- static bool loadCache() { \
- while (true) { \
- obj_class* obj = new obj_class; \
- if (obj != NULL) { \
- if (!obj->init()) { \
- return false; \
- } \
- } else { \
- break; \
- } \
- }; \
- obj_class##Pool::instance().freeAll(); \
- return true; \
- }
調(diào)用時(shí)在類(lèi)中加入如下代碼:
- // DECLARE_USING_OBJ_POOL(Bullet, BULLET_POOL_VOLUM, (NOT USING_DIRTY_DATA))
- DECLARE_USING_OBJ_POOL(Bullet, BULLET_POOL_VOLUM, USING_DIRTY_DATA)
LoadCache是游戲加載階段調(diào)用的,這里將進(jìn)行所有池對(duì)象的初始化。為此,你還需要實(shí)現(xiàn)init和purge函數(shù),分別是初始資源,銷(xiāo)毀資源 的,這些其實(shí)都只會(huì)被調(diào)用一次的。像狀態(tài)的初始化,大可放構(gòu)造函數(shù)中,每次使用對(duì)象構(gòu)造函數(shù)都會(huì)被調(diào)用的。外界是不能直接操作pool的。
如果池容量過(guò)小,分配失敗其實(shí)并不可怕。
見(jiàn)例子:
- // 大規(guī)模測(cè)試
- list<Entity*> timer;
- struct _Timer{
- list<Entity*>& _timer;
- _Timer(list<Entity*>& timer) : _timer(timer) {}
- void operator()() {
- for (list<Entity*>::iterator iter = _timer.begin();
- iter != _timer.end();) {
- Entity* entity = *iter;
- if (entity->isValid()) {
- (*iter)->update();
- } else {
- entity->destroy();
- iter = _timer.erase(iter);
- continue;
- }
- ++iter;
- } // end for
- }
- } update_timer(timer);
- const int num = 50;
- log << endl << "大規(guī)模測(cè)試:" << endl;
- for (int i = 0; i < num; ++i) {
- Entity* entity = ObjManager<Entity>::instance().make("Bullet");
- if (IS_VALID_POINTER(entity)) {
- log << " alloced index:" << i << endl;
- timer.push_back(entity);
- } else {
- log << " alloc bullet failed, waiting..." << endl;
- // 失敗了就多嘗試一次,反正任務(wù)量是20個(gè)
- --i;
- }
- update_timer();
- }
- // 不管使用什么模式都要自己回收所有的對(duì)象,
- // 不要依賴(lài)于池析構(gòu)時(shí)的對(duì)象釋放
- for (list<Entity*>::iterator iter = timer.begin();
- iter != timer.end(); ++iter) {
- (*iter)->destroy();
池容量為3,這是運(yùn)行結(jié)果:
[0sec] 加載緩存 [0sec] Bullet1 with HP:2 [0sec] init Bullet1 [0sec] Bullet2 with HP:3 [0sec] init Bullet2 [0sec] Bullet3 with HP:5 [0sec] init Bullet3 [0sec] 大規(guī)模測(cè)試: [0sec] Bullet10 with HP:5 [0sec] alloced index:0 [0sec] Bullet11 with HP:1 [0sec] alloced index:1 [0sec] Bullet12 with HP:1 [0sec] alloced index:2 [0sec] destroy entity11 [0sec] Bullet13 with HP:2 [0sec] alloced index:3 [0sec] destroy entity12 [0sec] Bullet14 with HP:3 [0sec] alloced index:4 [0sec] alloc bullet failed, waiting... [0sec] destroy entity10 [0sec] destroy entity13 [0sec] Bullet15 with HP:2 (這里省略很多行…) [1sec] alloced index:46 [1sec] Bullet57 with HP:4 [1sec] alloced index:47 [1sec] alloc bullet failed, waiting... [1sec] destroy entity55 [1sec] Bullet58 with HP:2 [1sec] alloced index:48 [1sec] alloc bullet failed, waiting... [1sec] alloc bullet failed, waiting... [1sec] destroy entity56 [1sec] destroy entity57 [1sec] destroy entity58 [1sec] Bullet59 with HP:5 [1sec] alloced index:49 [1sec] destroy entity59 [1sec] 釋放池 [1sec] purge Bullet59 [1sec] freeing sprite buf. size:3 [1sec] purge Bullet56 [1sec] freeing sprite buf. size:2 [1sec] purge Bullet57 [1sec] freeing sprite buf. size:1 請(qǐng)按任意鍵繼續(xù). . . |
附件下載