减少不必要的内存使用
放一张图,之前写了demo测试,对于使用ecs,不使用ecs,做instancing优化3中情况下,性能的差别。
可以看到如果你渲染的object在500以内,ecs性能并没有显著提升,当超过1000后,ecs性能有显著的优势,在10000obj下,差不多100的性能差距。
所以对于200内obj的游戏,是不是用ecs差别不大。
另外ecs这是unity提出的一个系统化的方案和标准,我们自己也可以或多或少使用传统方法做出类似的结果,没必要非ecs不可。
demo是如下图(Instancing),根据自带的rotate demo完成对应的instancing和传统方法版本,这个demo是1000个cube,有一个sphere旋转,撞到cube后,cube会自转一段时间,逐渐停止,所以需要1001个物体不停的update:
=========补充公司内分享的完整文章:
不再需要MonoBehaviour、Component和GameObject
以前MonoBehaviour承载了游戏逻辑和数据两部分功能,我们通过创建GameObject然后添加MB(MonoBehaviour,下同)然后通过Update来更新游戏逻辑,往往我们Update里就是更新一些数据,而MB的实现非常复杂,有很多不需要的功能都一股脑的被继承下来,这导致我们一个非常简单的任务可能需要浪费很多内存去应付那些不需要的功能,如果再无节制的使用MB,那基本就是游戏运行效率的噩梦。
之前的Component是继承自MB,大部分时候Component的作用就是提供数据,但是通过Component组织的数组是对CPU cache不够友好的,因为它并没有把需要多次重复计算更新的数据组织到一起,使得CPU在做计算时可能cache miss,如果游戏逻辑需要大量对象需要更新数据,可能这部分消耗是非常大的。
同时MB不能很好的解决执行顺序问题,对于动态创建的GameObject,MB的更新顺序是不确定的,我们往往系统某些MB的Update和Destroy在某些MB之后,以保证相互的依赖关系,但Unity的MB设计上没有更好的解决这个问题,通过调整Script Execution Order既麻烦也没啥卵用(之前测试对于动态创建的MB毫无作用,只能用于场景中静态的MB)。
还有,如果游戏中存在大量的GameObject,上面绑定大量的Component,那么执行大量的Update是非常耗时的,而且这些Update只能运行在主线程,无法并行。
为此,Unity 2018.2 引入了全新的ECS系统,用于解决上面提到的问题。
全数据化驱动
ECS的核心设计目标就是去掉传统的MB,GameObject,Component对象结构,而改为全数据化驱动的代码组织方式,通过这样的改变,可以解决2个问题:
1)将数据更有效的组织,提高CPU cache利用率;
2)并行化。
先简单看一个例子,这个是ECS sample自带的例子:
这个例子可以看到,虽然画面里有超过1000个物体,但并没有对应1000个GameObject,当球体碰到方块的时候,会产生自转后衰减,同时可以保持在300-600的fps运行帧率。这在以前,我们要实现类似的效果需要创建1000个GameObject,然后有1000个MB负责更新GameObject的transform信息,我按照这样的方法来实现,那么这个demo大概只有1/3的fps。
注意到上图会创建更多的GameObject,fps大概100-200fps之间,当然这么实现并不是最优化的,我们还可以使用Instancing优化drawcall,为了对比我又实现了Instancing的版本,对比如下:
fps大概150-300fps,可以看到instancing大概提高了1倍的fps,在1000 objs测试下,不同实现方法之间差别大概1-2倍,貌似差别不是很大,于是我又测试了更高obj数量的fps,在更高的objs测试下,得到如下图表:
可以看到在更高的obj数量下,ECS方法的优势就体现出来了,在10000 obj下,ECS任然可以做到350的高fps,而即便Instance优化,也只剩下4fps,差距几乎100倍。
现在我们回到开头,ECS解决了如下2个问题:
1)将数据更有效的组织,提高CPU cache利用率;
2)并行化。
但是如何解决的呢?
将数据更有效的组织,提高CPU cache利用率
传统的方法是将gameobject的数据放在Components上,比如可能是这样(参考1):
using Unity.Mathematics
class PlayerData// 48 bytes
{
public string public int[]
public quaternion
public float3
string name // 8 bytes
someValues// 8 bytes
}
PlayerData players[]
他的内存布局是这样:
而这样的设计对cpu cache是极度不友好的,当你试图批量更新float3数据时,cpu cache预测总是失败的,因为他们被分配到了内存中不连续的区域,cpu需要几次寻址跳转才能找到对应的数据并操作,如果我们能够将需要批量更新的数据连续的存放到一起,这将大大提高cpu cache的命中率,提升计算速度。
按ECS设计规范,就是将批量的更新的数据抽取出来,在内存中连续排列,从而在cache预读中能够将后续的数据一次性读取进来,提高cpu操作的效率,
在实际计算的时候,通过间接索引,来更新全部entity的各个类型的数据,而不是更新每个entity的全部数据,如图:
可以看到这里,最大的变化是:
在一个system(相当于以前的component)更新计算中,是将所有的entity的position放在一起批量更新的,因为这些position的float3数据在内中是连续的,cpu操作起来是最快的。
并行化
将数据单独提取出来后,接下来的任务就是将这些数据计算并行化,充分利用多核。在以前,几乎逻辑代码都是跑在主线程,当然也有项目组意识到了这个问题,会将一些和显示无关的逻辑放入多线程中并行,但都没有彻底的在数据上抽象形成一套完整的开发框架,而ECS解决了这个问题。
ECS开放了底层的job system系统,在上层提供了c# job system,它是一套多线程调度系统。如果不同的数据是无相互依赖的,仅需要将这些数据通过c# job system放入多个线程并行化计算就可以了,如:
public class RotationSpeedSystem : JobComponentSystem
{
struct RotationSpeedRotation : IJobProcessComponentData<Rotation, RotationSpeed>
{
public float dt
public void Execute(ref Rotation rotation, [ReadOnly]ref RotationSpeed speed)
{
rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.axisAngle(math.up(), speed.Value * dt))
}
}
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var job = new RotationSpeedRotation() { dt = Time.deltaTime }
return job.Schedule(this, 64, inputDeps)
}
}
如果不同的数据有依赖,需要其他的数据计算完才能完整计算,则可以设置任务依赖,c# job system会自动完成这样任务的调用和依赖关系排序。
混合模式
目前已经存在大量的传统方式开发的代码,如果想享受到ECS带来的高效,势必要将现有代码大幅改造,有没有相对简单的方法既能保持现有代码没有太大变动,又可以提高效率呢,答案是ECS提供一种兼容的混合模式。
例如如下代码(参考2):
using Unity.Entitiesusing UnityEngine
class Rotator : MonoBehaviour{
// The data - editable in the inspector public float Speed
}
class RotatorSystem : ComponentSystem{
struct Group
{
// Define what components are required for this // ComponentSystem to handle them. public Transform Transform
public Rotator Rotator
}
override protected void OnUpdate()
{
float deltaTime = Time.deltaTime
// ComponentSystem.GetEntities<Group> // lets us efficiently iterate over all GameObjects // that have both a Transform &Rotator component // (as defined above in Group struct). foreach (var e in GetEntities<Group>())
{
e.Transform.rotation *= Quaternion.AngleAxis(e.Rotator.Speed * deltaTime, Vector3.up)
}
}
}
主要的修改是把MB的Update函数移到了ComponentSystem的OnUpdate函数中,同时增加了一个Group的struct,用于在MB和ComponentSystem之间交换数据,同时在原GameObject上添加一个GameObjectEntity组件,这个组件的用途是将GameObject其他全部的Component抽取出来并创建一个Entity,这样就可以通过GetEntities函数在ComponentSystem中遍历对应的GameObject了。
Unity会在启动的时候,把所有挂载了GameObjectEntity组件的GameObject,都创建对应ComponentSystem,这样你任然可是使用以前的GameObject.Instantiate方法来创建GameObject。只是原本MB的Update函数被替换到了ComponentSystem的OnUpdate函数中。
通过这样的修改,你可以混合ECS的部分能力又相对保持原本的代码结果,总结来说:
混合模式可以分离数据和行为,可以批量化更新对象的数据,避免每个对象的virtual方法调用(抽象到了一个函数中),任然可以继续使用inspector来观察GameObject的属性和数据。
但是这样修改并没有彻底改善什么,创建、加载的时间没有改善,数据的访问任然是cache不友好的,并没有把数据在内存中连续排布,也没有并行化。
只能说这样修改是为了进一步靠近纯ECS做的阶段性代码重构,最终还是要完全使用纯ECS才能获得最大的性能提升。
如果对楼主有帮助,给个采纳好不,谢谢啦
Photon和KBEngineunity3d是最适用Unity3d游戏开发的两个服务器引擎,但它们还是有区别的,只有清楚地了解区别在哪才能正确使用,下面简单描述下两者的共同点和不同点。
语言
对于大部分的程序员语言简直就是宗教信仰。
Photon使用C#开发,当然使用者也是用C#进行各类游戏功能开发。前后端同种语言,这对使用Unity3d游戏开发也有很大的好处。
KBEngine使用C++开发,逻辑开发是用python,也是很不错很快速的。
开源与收费情况
Photon是Exit Games公司的产品,不开源,有好多种收费模式,官网上可以看到。开发阶段可以用免费的license,后期可以看流量用户活跃度来选择付费模式。后续的支持,似乎是免费的,你可以选择邮件或是到论坛发帖求助,当然是E文。
KBEngine是国人开发,开源免费,但从官网上并没有看到商业使用的案例。有中文论坛,你可以在论坛上向开发者求助。
虽然两者的模式不同,但作为一个Unity3d游戏开发者,我们最希望的其实是把游戏引擎当作一个安全稳定的黑箱。
操作系统
之前说了Photon使用C#开发很自然的,配套的工具也是使用C#,比如最重要的PhotonControl。所以开发环境和生产环境最好都是windows。
虽然在跨平台上有mono,在服务器代码部分是系统无关的,但是不管你信不信,我是不信它的一套窗体工具也能运行在Linux下。反正,官网说法是,开发和生产环境都是用windows。
KBEngine建议开发环境选择Windows,生产环境选择linux。毕竟你总不希望开一组服务器打开9个Console窗体,一不小心把哪个点X了吧~
协议
Photon有自己的序列化反序列化方式,你也可以使用protobuf这类的来做应用层传输协议。
KBEngine在这方面表示不支持自定义协议,它帮你选择了有效的方法来处理,如果你习惯了他规定的方式,会喜欢上的。
看法
在功能上,我毫无疑问地更喜欢KBEngine,脚本化和自动持久化是极富魅力的功能。而Photon几乎没做这方面的功能,可能和老外的观念有关系。就目前我对两者功能的理解看来,Photon其实是个和SuperSocket差不多的东西,而SS是作为轻量级服务器框架存在的,Photon却是说自己是Unity3d游戏引擎,除去提供的MMO示例代码(未解读),没看到什么游戏引擎的魅力。
unity出现连接到服务器意思是unity程序开始运行了。根据查询相关公开信息,当程序运行的时候,它就显示开始连接服务器。Unity是跨平台游戏引擎开发商,是实时3D互动内容创作和运营平台。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助Unity将他们的创意变成现实。欢迎分享,转载请注明来源:夏雨云
评论列表(0条)