一聚教程网:一个值得你收藏的教程网站

热门教程

对象池加速游戏内存分配的解决方案

时间:2022-06-25 04:52:19 编辑:袖梨 来源:一聚教程网

相比内存池,对象池更易用更容易管理,而且还可以利用脏数据,也就是上次被回收掉的对象的数据。而且偶尔的空间分配失败其实不是那么重要(后面会讲怎么在会失败的情况下完成分配任务),游戏中还是速度更重要些。

原理

一次申请大量连续内存(整数个对象大小),最好用堆,当然如果用栈数组也没人拦你,栈空间可是相当有限…

由于分配的对象生存期是不固定的(如下图),池不可能保持已分配对象的连续性,这时进行块移动会降低程序效率。

  分配       分配 分配   分配  

所以需要把闲置对象的指针放入容器中来管理。此容器必须能快速存取删,而且不需要频繁大距离移动容器元素指针,最好是刚从容器中释放的元素能马上让下一个元素使用,这时候栈就是一个很好的选择了。初始时将所有闲置对象指针压入栈,分配时pop,栈为空时返回空;释放时将对象指针push进栈即可。

实现

其实boost已经提供了对象池了,那为什么还要自己实现一个呢?当然是要方便DIY了…其实你也可以用boost的对象池来第二次封装

这部分直接参看附件源码吧

使用

这才是真正的重点

分配时直接用Sobot* p = ObjPool::alloc()?不,还应该使用placement new调用其构造函数:

new(p) Sobot()

你想在你的代码中充斥大量这样的代码吗?放到工厂里面也许是一种办法,但是工厂引用到了对象池了。而大师告诉我们好的设计要保持职责单一,用与不用对象池应该不影响原系统的正常运行。而且还有一点,用这种办法,就只能和某些组件绝缘了,比如智能指针。

此时重载new与delete就至关重要了:

static void* operator new(size_t) {

    return SobotPool::instance().alloc();

}

static void operator delete(void* p) {

    SobotPool::instance().free(reinterpret_cast(p));

}

一个对象中往往充斥着大量指针,而这些指针指向的空间往往大于包含他们的对象本身。如果将这些指针所在在类也应用对象池,一方面是池的容量你无法估计,另一方面是使用起来麻烦。而且你也无法向上面这样给每个类注入new与delete的重载。用代理?呵呵,项目中估计会出一堆问题。这时候我们不妨使用脏数据,也就是说对象池中保存的对象全是可以直接使用的对象,而并非空对象,对象中的成员指针变量引用到的内存不在池中。为了保证安全,清空这些内存在池销毁时进行。

和上面的功能一起,我们可以定义一个宏,免得每次使用都得重复大量代码。如下:

#define USING_DIRTY_DATA true

// 如果不是方便测试需要,可以将这行

// typedef ObjPool obj_class##Pool;

// 标注为private

#define DECLARE_USING_OBJ_POOL(obj_class, max_size, _using_dirty_data)

    public:

        typedef ObjPool 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(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;

    }

调用时在类中加入如下代码:

    // DECLARE_USING_OBJ_POOL(Bullet, BULLET_POOL_VOLUM, (NOT USING_DIRTY_DATA))

    DECLARE_USING_OBJ_POOL(Bullet, BULLET_POOL_VOLUM, USING_DIRTY_DATA)

LoadCache是游戏加载阶段调用的,这里将进行所有池对象的初始化。为此,你还需要实现init和purge函数,分别是初始资源,销毁资源的,这些其实都只会被调用一次的。像状态的初始化,大可放构造函数中,每次使用对象构造函数都会被调用的。外界是不能直接操作pool的。

如果池容量过小,分配失败其实并不可怕。

见例子:

   // 大规模测试

    list timer;

    struct _Timer{

        list& _timer;

        _Timer(list& timer) : _timer(timer) {}

        void operator()() {

            for (list::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 << "大规模测试:" << endl;

    for (int i = 0; i < num; ++i) {

        Entity* entity = ObjManager::instance().make("Bullet");

        if (IS_VALID_POINTER(entity)) {

            log << "  alloced index:" << i << endl;

            timer.push_back(entity);

        } else {

            log << "  alloc bullet failed, waiting..." << endl;

            // 失败了就多尝试一次,反正任务量是20个

            --i;

        }

        update_timer();

    }

    // 不管使用什么模式都要自己回收所有的对象,

    // 不要依赖于池析构时的对象释放

    for (list::iterator iter = timer.begin();

        iter != timer.end(); ++iter) {

        (*iter)->destroy();

    }

池容量为3,这是运行结果:
[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]
大规模测试:
[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
请按任意键继续. . .

热门栏目