i5-4430 @ 3.00GHz
Average 13.71ms [Full anim key - linear search]
Average 11.13ms [Full anim key - binary search]
Average 8.23ms [Data only key - linear search]
Average 7.79ms [Data only key - binary search]
Average 1.63ms [Pre-indexed - binary search]
Average 1.45ms [Pre-indexed - linear search]
参考资料
(usage:: 什么是 data oriented design )
数据从一种形式转换为另一种形式
,而应尽可能以最有效的方式完成这种转换data oriented programming vs data oriented design
什么是 ECS
复杂的数据交互,同时允许未来的功能添加或重构
一个唯一的标识符来实现的
,该标识符代表游戏中的entity
。component
的数据片段与这些标识符关联起来,这些标识符就变得有意义了。ECS 数据模型的强大之处
match
与之相关的组件集的实体进行操作。行为的改变是通过组件组合而不是继承实现的
。querying
程序中的所有数据,并在所有与该查询相匹配的实体上运行,ecs 是弱关系模型
某些数据关系和约束时存在局限性
,它实际上是关系型数据模型的一个子集
所有玩家都有许多同为玩家的好友,我该如何为好友列表建模?
某些类型的数据(如游戏世界中的实体)进行建模时非常强大
,对所有类型的应用数据进行建模时还不够通用
对某些类型的数据进行建模会感到别扭,或者硬塞进数据模型中
。关系模型实现数据模型
必须有一个指向实体 id 的唯一外键引用。
为什么采用 ECS 这种弱关系模型而不是关系模型
不是因为数据库速度慢,而是因为在与数据库交互时,往往是通过网络将数据持久化到磁盘上
,作者的观点
一个结构化的框架来评估它们
。且结构化的方式来表示和转换的应用数据
,挑选哪些特性和性能权衡对的特定应用最有意义
,通过它能根据标准指标评估系统的性能
。OOP 已死
很少考虑哪些类组成一个独立的、可重用的、一致的逻辑单元
。如何处理
对于任何有重要数据量的数据,设计都会类似于数据库。
basic data operation 基本原则
heterogeneous string map
.statement managment
basic concurrent control
(usage:: zig 作者 dop 观点 )
memory layout
use indexes instead of pointers
store boolean out-of-band
eliminate padding with struct of arrays
Store sparse data in hash map
encoding approaches
保留两个数组 dead_creatures 和 living_creatures 来完全避免使用该位。
handlers are the better pointers
要点
Move memory management into central systems
不能简单地立即销毁资源对象
,因为该资源可能仍然中等待被 GPU 消耗的命令列表所引用。Group items of the same type into arrays
额外的优点是
public index-handles 而不是指针
好处
公众提供数组索引
,而不是将内存指针交给外部世界。优点
内存安全考虑
每种类型的句柄都应该有自己的 C/C++ 类型
,试图将错误的句柄类型传递给一个函数的行为在编译时就已经被发现了优化的地方
存在性处理
数据结构(如数组)和避免使用 NULL 指针
,能显著减少控制流程的数量。复杂度
控制流复杂性
。软件固有的复杂性还有另一种形式,那就是状态的复杂引用计数或垃圾回收机制
。操作的函数不确定数据是否存在或确保边界得到遵守时,这些技术会增加复杂性
为什么要使用 if
决定消除控制流是一个值得考虑的目标
,那么必须开始理解消除哪些控制流操作。保证数据都不会为 NULL
处理类型
不要使用 bool 值
数据和信息之间的区别
以声明某些东西存在的文字字符串
,直到比如一个单一的位标志,以表明一个东西可能有一个属性。算术编码等高级算法或利用领域知识来存储比位更少的信息
。一个例子
如果你完全健康,那么你就不需要再生
一旦你中枪,你需要一段时间才能开始再生
一旦死亡就无法再生
一旦死了就没有健康可言了
数据可能导致缓存行利用问题的常见问题
查看流控制语句来改善这一点
上次造成伤害后足够长的时间内,重新生成函数才需要运行
。为常见情况组织数据布局
包含布尔值是第一个函数的 isHurt 变量
。isDead 的布尔值现在是隐式的,也意味着健康值为 0,能减少许多其他系统的处理
。节省了一个浮点比较或将其转换为布尔值
。不要经常使用枚举
使用表来提供所需的所有信息
,因为只有两个表都能用各种表来模拟
。只需要每个枚举值一个表
。设置枚举是插入表或从一个表迁移到另一个表
因为它需要检查代表该实体状态的所有表
。基于外部状态进行操作, 要么是确定一个实体是否处于适合进行操作的正确状态
。因为访问外部状态在纯函数中无效
,解决办法是运行转换
,将每个 switch 或虚拟方法的内容作为操作应用到相应的表,即与原始枚举值相对应的表。考虑一个辅助表来表示处于兼容状态
。不使用枚举来控制指令流时,不处理它们是行的
。因为在表之间移动对象也是有成本的
。包括键绑定、颜色枚举或小有限值集的好名称。
枚举用于使对那些更大或更难记住的数据表的访问合理化
。多态的实现
枚举作为类型变量
。将虚函数实现为基于该类型的开关
,或者作为函数数组
。要允许运行时加载库
,那么将需要一个系统来更新调用哪些函数
。。运行时多态
实例仅由于实例的性质而以不同方式对public interface 做出反应的能力
。编译时多态性通过模板和重载来实现
。类在编译时类类型未知的情况下为公共基本操作提供不同实现的能力
。在由 this 指针指向的内存开头的虚表指针中的类型在运行时调用正确的函数
。类能根据其类型以不同方式对公共调用签名做出反应,但其类型能在运行时更改
。它能根据状态以及核心语言运行时虚表查找提供不同的反应
。但由于调度机制建立在动态查找之上
,例子
事件处理
自身在表中的存在作为注册技术,使得操作更简便,订阅变为插入,取消订阅变为删除
,存在全局表用于订阅全局事件以及具名表
,具名表能让订阅者在发布者存在之前进行订阅。有立即触发转换或先排队新事件直至整个转换完成再一次性分发的选择
。componet based objects
there is no entity
数据和意义的分离
事实只是原始数据
。事实与意义的分离在面向对象的方法中是不可能的
,这就是为什么每次事实获得新的意义时,意义都必须作为包含事实的类的一部分来实现。搜索
索引
实现一个即时索引系统,以提供相同类型的性能改进
。data-oriented lookup
例子
识别需要哪些数据来满足程序的要求
考虑的角度
一个算法的两种不同数据布局可能比使用的算法产生更大的影响。
finding lowest or highest is o sorting problem
在某些情况下,甚至不需要搜索。
如果搜索的原因是在一个范围内找到一些东西,比如找到最近的食物、住所或掩护,那么问题实际上不是搜索,而是排序。
在查询的前几次运行中,搜索可能会进行真正的搜索来找到结果,但是如果它运行得足够频繁,就
没有理由不将查询提升到其他一些表数据的运行时更新排序子集
。如果需要最近的三个元素,那么保留最近三个元素的排序列表,当一个元素有更新、插入或删除时,使用该信息更新排序后的三个元素。
对于使不在集合中的元素更接近的插入或修改,在将新元素添加到排序后的最佳元素之前,检查元素是否更接近并弹出最低值。
如果有删除或修改使排序集中的一个成为消除的竞争者,可能需要快速检查其余元素以找到新的最佳集。
性能
耗时最多的地方
都在于坚持不过早地进行优化
对象的集合视为个体集合
什么时候开始优化
type/system #keyword/dop #public