zl程序教程

您现在的位置是:首页 >  其他

当前栏目

ECS的简单入门(一):概念

2023-02-26 10:18:40 时间

CPU与缓存(Cache)
首先我们先来了解一下CPU读取数据时的操作,首先CPU会先从自己的缓存中去查找,如下图,有L1/ L2/ L3三级缓存,若缓存中没有找到需要的数据,则会去内存中查找(我们称之为Cache Miss),CPU读取到内存数据后就会将新数据存放在缓存当中。CPU访问内存的速度会比访问L1 Cache的速度慢100倍,因此提高缓存命中率(Cache Hit),避免Cache Miss会大大提高性能。因此我们应该尽量使用数组,尽量分割属性(SOA),尽量连续的进行处理。

这也使得一味的讨论复杂度O(n)不再适用,因为现在效率=数据+代码,最常见的例子就是在数据量小的情况下遍历数组会比 (Hash)Map 快上很多

(福利推荐:阿里云、腾讯云、华为云服务器最新限时优惠活动,云服务器1核2G仅88元/年、2核4G仅698元/3年,点击这里立即抢购>>>

缓存行
缓存又由若干个缓存行(cache line)组成,每个缓存行大概占64字节。

假设我们现在想要旋转并移动场景中的一个物体,那么我们会修改它的Position(Vector3数据,3个float)和Rotating(Quaternion,4个float),其中1个float占4字节,那么一共占28字节,那么在这个缓存行就会有36个字节是完全浪费的,甚至我们可能只改了Position的x的值,这样浪费的就更多。若有上千个这样的方块,那么我们缓存中可能就会存在超50%的内存垃圾,从而导致缓存命中率大大的降低。

问题的引出
了解了上面的知识之后,我们再看回Unity,在传统模式下,我们在场景中创建一个Cube,上面会有Transform,MeshRenderer,Collider等组件,而这些组件在内存中的排放都是无序的,这就会降低我们的缓存命中率。

除此以外,像我们前面说到的旋转移动方块,我们只用到了Position和Rotating两个属性,但是使用的时候整个Transform都会被加到缓存当中,而Transform中有很多我们不需要的属性占用了不少的缓存空间,同样的降低了我们的缓存命中率。

使用ECS就可以解决上述的这些问题,从而提高性能。

ECS概念
ECS即Entity Component System,是Unity的一种框架,我们可以把Entity看做是一个唯一id,Component看做是数据,其本质是一个存放数据的Struct,一个Entity上可以拥有多个Component,但是Component中不会有任何逻辑处理。而System则可以根据Entities索引读取对应的Component,对Component中的数据进行处理。

如上图,有三个Entity,Entity A,Entity B,Entity C,与它们相关联的Translation,Rotation,LocalToWorld和Renderer这些都是Component。同时还有一个System,用来将Translation和Rotation两个Component内的值相乘并赋予LocalToWorld。

我们可以设置System需要Renderer Component,那么Entity C将被该System忽略。或者设置System处理的Entity不能有Renderer Component,那么Entity A和Entity B将被忽略。

Archetypes
不同的Component可以有多种不同的组合,每种组合我们即称之为Archetype。例如下图,Entity A和Entity B的Component的组合是相同的,所以他们都属于Archetype M,而Entity C由于少了Renderer,所以是另一种组合,属于Archetype N。

由于可以在运行时给Entity动态的添加或删除Component,若我们将Entity A的Renderder删除,则此时Entity A会隶属于Archetype N,若再删除Rotation,则剩下的Component组合和Archetype M,Archetype N都不相同,就好隶属于一个新的Archetype。

Memory Chunks
ECS会根据Archetype来进行分配内存,每个内存块我们称之为Chunk,ECS会将符合Chunk对应Archetype的Entity放在该Chunk当中。一个Chunk中,内存地址是连续的,大小固定为16KB。若Chunk装满了,则会生成一个新的Chunk用来存储新生成的且Archetype符合的Entity。

由于我们动态的添加或删除Entity的Component,会导致其Archetype变化,因此ECS也会改变其Chunk,放到与之对应Chunk中。

可能描述的不太好,我们来看示例图。下图中有三种Archetype,分别对应三种Component组合。每个Archetype都会有对应的Chunk用来存储对应的Entity。若Chunk存储满了,就会在对应Archetype下新生成一个Chunk。

这样的设计理念使Archetypes and Chunks是一对多的关系,同时若给定一个Component组合,我们要找到所有对应的Entity,只需要搜索现有的archetype即可,而不需要遍历所有的Entity。

ECS不支持使用特殊的排序来将Entity存储进Chunk中,若有一个Entity被创建或者被改变,使其隶属于一个新的Archetype时,ECS会将其存储在该Archetype下第一个还有空间的Chunk中。若有一个Entity被从Chunk中移除,ECS则会把该Chunk中最后一个Entity与其对应的Component移到这个空缺的位置中。

举例
关于Entity,Component,Archetype和Chunk的关系,我们在此举个简单的例子。假设我们有两个Component:C1和C2,然后我们生成五个Entity,其对应的Component分别为:E1(C1,C2),E2(C1),E3(C1,C2),E4(C1,C2),E5(C2)。由于组合分别有C1,C2,C1C2三种,所以会有三个Archetype,假设我们一个Chunk只能存储两个Entity。那么最终结果为:

Archetype1:Chunk1 [ E1(C1,C2),E3(C1,C2) ] -> Chunk2 [ E4(C1,C2) ]

Archetype2:Chunk1 [ E2(C1) ]

Archetype3:Chunk1 [ E5(C2) ]

结合到我们前面有关缓存的知识,若我们要移动选择一个方块,可能会写两个Component,MoveComponent存x和y两个float(假设只前后左右移动),RotateComponent只存y一个float(假设只旋转y轴),这样我们的方块就只会有3个float,占用12个字节。若有多个相同的方块,由于都有上述两个Component,也就是属于同一种Archetype,因此内存也是连续的。那么在一个64字节的缓存行中,我们可以放下5个这样的方块数据,只有4字节是浪费的,就会大大降低Cache Miss。

ECS的简单入门(一):概念


本站部分内容转载自网络,版权属于原作者所有,如有异议请联系QQ153890879修改或删除,谢谢!
转载请注明原文链接:ECS的简单入门(一):概念

你还在原价购买阿里云、腾讯云、华为云、天翼云产品?那就亏大啦!现在申请成为四大品牌云厂商VIP用户,可以3折优惠价购买云服务器等云产品,并且可享四大云服务商产品终身VIP优惠价,还等什么?赶紧点击下面对应链接免费申请VIP客户吧:

1、点击这里立即申请成为腾讯云VIP客户

2、点击这里立即注册成为天翼云VIP客户

3、点击这里立即申请成为华为云VIP客户

4、点击这里立享阿里云产品终身VIP优惠价

喜欢 (0)
[[email protected]]
分享 (0)