如何避免实体组件系统(ECS)中与1:N(pirateship
-cannon
)关系相关的缓存丢失?
例如,PirateShip
可以具有1-100 cannons
。 (1:N)
每个cannon
可以随时自由地分离/附加到任何pirateship
。
对于某些reasons,PirateShip
和Cannon
应该是实体。
内存图
在大约第一个时间步中,当逐渐创建ship
/ cannon
时,ECS内存看起来非常漂亮:-
图片备注:
ShipCom
和CannonCom
实际上是紧凑的数组。 从
cannon
中访问ship
信息的速度非常快,反之亦然(伪代码):-Ptr<ShipCom> shipCom=....;
EntityPtr ship = shipCom; //implicitly conversion
Array<EntityPtr> cannons = getAllCannonFromShip(ship);
for(auto cannon: cannons){
Ptr<CannonCom> cannonCom=cannon;
//cannonCom-> ....
}
问题
在随后的时间步中,将随机创建/销毁一些
ship
/ cannon
。结果,
Entity
,ShipCom
和CannonCom
数组周围有间隙散射。当我想分配它们时,我将从池中获得一个随机的内存块。
EntityPtr ship = ..... (an old existing ship)
EntityPtr cannon = createNewEntity();
Ptr<CannonCom> cannonCom= createNew<CannonCom>(cannon);
attach_Pirate_Cannon(ship,cannon);
//^ ship entity and cannon tend to have very different (far-away) address
因此,上面的“真正快速”的代码变成了瓶颈。 (我分析了。)
(编辑)我认为,底层缓存未命中也可能是同一艘船内加农炮之间的不同地址造成的。
例如(
@
是炮塔组件的地址),ShipA
将turret@01
转换为turret@49
ShipB
将turret@50
转换为turret@99
在以后的时间步中,如果将
turret@99
移到ShipA
,它将是:-ShipA
将turret@01
转换为turret@49
+ turret@99
(内存跳转)ShipB
将turret@50
转换为turret@98
问题
什么是设计模式/ C++魔术可以减少频繁使用的关系导致的缓存丢失?
更多信息:
relation_Pirate_Cannon = (ShipCom:CannonCom)
,relation_physic_graphic = (PhysicCom:GraphicCom)
Entity
/ ShipCom
/ CannonCom
的数量没有限制。我不想在程序开始时限制它。
我想到的第一个解决方案是启用重定位,但是我认为这是不得已的方法。
最佳答案
一种可能的解决方案是添加间接的另一层。它会使事情放慢一点,但有助于保持阵列紧凑,并可以加快整个过程。剖析是唯一知道是否真的有帮助的方法。
话虽这么说,该怎么做?
Here是稀疏集合的简要介绍,值得一读,以便更好地理解我的意思。
与其在同一数组中的项目之间创建关系,不如使用第二个指向的数组。
让我们将两个数组称为反向和直接:
每当您添加大炮时,都将获取其实体标识符和直接数组中的第一个空闲插槽。使用您的实体标识符设置
slot.entity
,并将reverse[entity]
放入插槽的索引。每当您放下东西时,都复制直接数组中的最后一个元素以使其紧凑并调整索引,以保持关系。船会将索引存储到外部数组(反向),以便您可以自由地在内部数组(直接)之间来回切换。
优点和缺点是什么?
好吧,每当您通过外部阵列访问大炮时,由于存在额外的间接层,您都会有一个额外的跳跃。无论如何,只要您成功地减少了以此方式进行的访问次数,并且您访问了系统中的直接阵列,就可以使用紧凑的阵列进行迭代,并且使高速缓存未命中的次数最少。
关于c++ - 避免与实体组件系统中与1:N关系相关的缓存未命中,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45316989/