欢迎光临散文网 会员登陆 & 注册

《游戏编程模式》笔记——数据局部性

2023-09-20 04:10 作者:黑白色的枫  | 我要投稿

意图

合理组织数据,充分使用CPU的缓存来加速内存读取。

模式

现代的CPU有缓存来加速内存读取。它可以更快的读取最近访问过的内存的毗邻内存。通过提高内存局部性来提高性能——保证数据以处理顺序排列在连续内存上。

何时使用

使用数据局部性的第一准则是在遇到性能问题时使用。

就本模式而言,还要确认你的性能问题确实由缓存不命中引发,如果是其他原因,这个模式帮不上忙。

设计决策

如何处理多态?

不使用多态:

避免子类,至少内存优化的部分避免使用。简洁安全,我们明确的知道处理的是什么类,所有的对象大小相同。更快,动态调用意味着在跳转表中寻找方法,然后跟着指针寻找特定的代码。这个小号会因为不同的硬件区别很大,但动态调用总是会有代价。不灵活,使用动态调用是为了不同对象间展示不同的行为,可以使用虚方法处理。

为每种类型使用分类的数组。对象被紧密排列。静态调度。获得了对象的类型,就不必使用多态,可以使用常规的非虚方法调用。但是需要追踪每个集合,多个类型的时候维护数组会很麻烦。由于我们为每种类型管理分离的集合,我们无法解耦类型集合。

使用指针集合:

使用指针数组指向基类或者接口类型,可以使用多态。

灵活,这样构建集合的代码可以与任何支持接口的类工作,完全开放。

对缓存不友好。指针跳转导致缓存不好有,如果不是性能攸关的,很有可能行得通。

游戏实体是如何定义的?

如果游戏实体是拥有它组件指针的类:

纯OOP的解决方案中,我们拥有游戏对象类,以及指向它拥有的组件的指针,但是不知道组件是如何在内存中组织的。

我们可以将实体存储到连续数组中,游戏实体不在乎组件的位置,我们就可以将组件组织到数组中,优化遍历。

我们拿到一个实体,就可以轻易的获得它的组件,就在一次指针跳转后的位置。

在内存中移动组件很难。组件启用或关闭时,如果想要在数组中移动它们,保证启用的组件位于前列。如果实体中有指针指向组件时直接移动该组件,指针可能会被销毁,得保证同时更新指向组件的指针。

如果游戏实体是拥有组件ID的类:

使用ID或索引来查找组件,需要为每个实体保存独特的ID,遍历数组查找,或者使用哈希表将ID映射到组件现有位置。

但是更复杂,需要实现并排除漏洞,会消耗内存。

需要访问组件“管理器”。基本思路是用抽象ID标识组件,以此来获得对应组件对象的引用。但是需要让ID有办法找到对应的组件。通过裸指针,游戏实体可以直接找到组件,但是需要我们接触游戏实体和组件注册器。

如果游戏实体本身就是一个ID:

实体干的唯一事情就是讲组件连接在一起,定义了一个存在于游戏世界中的实体。

实体很小,想要传递游戏实体的引用时只需要一个简单的值。

实体是空的,必须将所有组件移出,不能再拥有组件独有的状态和行为,这样更加依赖组件模式。

不必管理实体的生命周期,实体是内置值类型,不需要显示分配和释放。实体的所有组件被释放时,对象就隐式死亡了。

查找实体的某一组件可能会慢。为了找某个实体的组件,需要给ID做对象映射,这一过程消耗可能会很大。

也可以使用组件在数组中的索引作为ID,需要我们保持组件数组完全同步,但我们就无法独自排序某个数组。


这一章大部分围绕着组件模式。这种模式的数据结构是为缓存优化的最常见例子。

这一模式几乎完全得益于同类对象的连续存储数组,随着时间推移,我们需要向数组增加或删除对象时,可以使用对象池模式。

游戏引擎Artemis是首个也是最著名的为游戏实体使用简单ID的游戏框架。


参考

《游戏编程模式》

《游戏编程模式》笔记——数据局部性的评论 (共 条)

分享到微博请遵守国家法律