深入理解对象池技术

您所在的位置:网站首页 对象池技术的特点 深入理解对象池技术

深入理解对象池技术

2024-05-07 04:39| 来源: 网络整理| 查看: 265

对象池在游戏开发中使用的非常广泛,尤其对于内存的管理方面,很多人知道对象池但是不理解,本篇文章就使用通俗的语言给大家详细讲解

游戏中通过重新使用固定池中的对象来提高性能和内存使用,而不是单独分配和释放对象。  举例,游戏的视觉效果, 当英雄施放一个咒语时,我们想要一阵闪闪发光的闪光, 这就需要一个粒子系统,粒子系统是引擎必须要支持的。

粒子播放时,可能导致数百颗粒子产生,系统需要能够很快地创建它们, 更重要的是,我们需要确保创建和销毁这些粒子不会导致内存碎片。

移动设备的编程在许多方面比传统的PC编程更接近嵌入式编程,内存稀缺,用户期望游戏稳定,高效的压缩内存管理器很少可用。 在这种环境中,内存碎片是致命的。

碎片意味着我们堆中的可用空间被分解成较小的内存块,而不是一个大的开放块。 可用的总内存可能很大,但最大的连续区域可能会很小。 假设我们已经有十四个字节可用,但是它们被分成两个七字节的片段,它们之间带有大量的使用内存。 如果我们尝试分配一个十二字节的对象,我们将失败。 如下所示:

由于碎片,因为分配可能很慢,游戏对于管理内存的时间和方式非常谨慎。 一个简单的解决方案通常是最好的 - 在游戏开始时抓住大块内存,并且不要释放它直到游戏结束。 但是,对于在游戏运行期间需要创建和销毁东西的系统来说,这是一个痛苦。 对象池给了我们两个世界最好的, 对于内存管理员,我们只是在前面分配一大块内存,而不是在游戏中释放它。 对于池的用户,我们可以自由地分配和释放对象到我们心脏的内容。

定义一个维护一个可重用对象集合的池类。 每个对象都支持一个“在使用中”查询来判断当前是否“活着”。 当池初始化时,它将在前面创建整个对象集合(通常在单个连续分配中),并将它们全部初始化为“未使用”状态。 当你想要一个新对象时,查询一个池, 它找到一个可用对象,将其初始化为“正在使用”,并返回。 当对象不再需要时,它被设置回到“不使用”状态。 这样,可以自由地创建和销毁对象,而不需要分配内存或其他资源。

  这种模式在游戏中广泛应用于诸如游戏实体和视觉效果之类的明显的东西,但它也用于不太可见的数据结构,例如当前播放的声音。 使用对象池时: 一、您需要频繁地创建和销毁对象。 二、对象的大小相似。 三、堆上分配对象很慢或可能导致内存碎片化。 四、每个对象都封装了一个资源,例如数据库或网络连接,这些资源很昂贵,可以被重用。

你通常依靠一个垃圾收集器或者新的和删除来处理你的内存管理, 通过使用对象池,您说的是“我更好地了解这些字节应该如何处理”。这意味着你可以处理这种模式的局限性。

需要根据游戏的需要调整对象池的大小, 调整时,当池太小时,通常很明显(没有什么像碰撞引起你的注意), 还要注意池不算太大, 一个较小的池释放可用于其他东西的存放。

大多数池实现将对象存储在一个对象数组中, 如果所有的对象都是相同的对象,那就行了。 但是,如果要在池中存储不同类型的对象或可能添加字段的子类的实例,则需要确保池中的每个插槽具有足够的可用对象的内存。 否则,一个意想不到的大对象会踩下下一个对象并将其记录在内。 同时,当您的对象尺寸不同时, 每个插槽需要足够大以容纳最大的对象,如果对象很少,那么每次在该插槽中放一个较小的对象时,都会丢弃内存。 这就像通过机场安全,并为您的钥匙和钱包使用一个巨大的携带大小的行李托盘。 当你发现自己使用了很多内存的时候,可以考虑将对象池分成不同大小的物体 - 不同大小的物品 - 行李箱的大托盘,小容器的小托盘。

大多数内存管理器都有一个调试功能,可以将新分配或释放的内存清除为一些明显的数值,如0xdeadbeef。 这可以帮助您找到由未初始化的变量引起的错误,或者在释放后使用内存。 由于我们的对象池在重用对象时不再经历内存管理器,所以我们失去了这个安全网,更糟糕的是,用于“新”对象的内存以前拥有完全相同类型的对象。 这使得几乎不可能告诉您是否在创建新对象时忘记初始化某些内容:存储对象的内存可能已经包含了几乎正确的数据。 因此,请特别注意,初始化池中新对象的代码完全初始化对象,甚至可能花费一点时间添加一个调试功能,以便在对象被回收时清除对象插槽的内存。

对象池在支持垃圾收集的系统中不常见,因为内存管理器通常会为您处理碎片。 但是池仍然有用,以避免分配和释放的成本,特别是在CPU速度较慢的移动设备和更简单的垃圾收集器上。 如果您使用对象池与垃圾收集器协同工作,请注意潜在的冲突。 由于池不再使用对象时不会重新分配对象,所以它们保留在内存中。 如果它们包含对其他对象的引用,它将阻止收集器再次收回它们。 为避免这种情况,当一个池化对象不再使用时,请清除其它对象所引用的任何引用。

现实世界的粒子系统通常会应用重力,风力,摩擦力等物理效应。 我们简单得多的样品只能将粒子沿直线移动到一定数量的帧,然后杀死粒子。 我们将从最简单的实现开始。 首先是小粒子类:

class Particle  {  public:    Particle()    : framesLeft_(0)    {}      void init(double x, double y,              double xVel, double yVel, int lifetime)    {      x_ = x; y_ = y;      xVel_ = xVel; yVel_ = yVel;      framesLeft_ = lifetime;    }      void animate()    {      if (!inUse()) return;        framesLeft_--;      x_  = xVel_;      y_  = yVel_;    }      bool inUse() const { return framesLeft_ > 0; }    private:    int framesLeft_;    double x_, y_;    double xVel_, yVel_;  };  默认构造函数将粒子初始化为“未使用”。 稍后调用init()会将粒子初始化为活动状态。 粒子随着时间的推移使用命名animate()函数,每帧应该调用一次。池需要知道哪些粒子可以重用, 它从粒子的inUse()函数中获取。 此功能利用了粒子的使用寿命有限的事实,并使用_framesLeft变量来发现哪些粒子正在使用,而不必存储单独的标志。

池类也很简单:

 copy

class ParticlePool  {  public:    void create(double x, double y,                double xVel, double yVel, int lifetime);      void animate()    {      for (int i = 0; i 


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3