unity要学ecs吗_ECS的泛泛之谈

18f37fff0e81ff302e90fe8e5d573f1a.png

这篇文章将带着你从设计出发重新发现ECS。

注意:此篇为泛泛之谈,不涉及具体实现。


从Abstract说起

  1. 从”是”到”能”再到”有”

对对象的抽象是整理代码的要点,继承是一种比较古老并常见的抽象,其描述了一个对象"是"什么,其中包含了对象拥有的属性和对象拥有的方法,在简单情况下,继承是一种非常易用易懂的抽象,然而在更复杂的情况下,继承引入的的问题渐渐浮现出来,使得它不再那么易用。

以下列举几个例子:

  • 深层次继承树(要理解一个类,需要往上翻看非常多的类)。
  • 强耦合(修改基类会影响到整棵子继承树)。
  • 菱形继承(祖父的数据重复,方法产生二义性)。
  • 繁重的父类(子类的方法被不断提取到父类,导致父类过度膨胀,某 UE4)。
  • 而这些问题又相互影响产生恶性循环,使得项目的后期开发和优化变得无比困难。

4f103d51f2dedda7ddcb1617e04b554f.png
万恶之源

于是,大家便尝试简化模型,并描述了一种叫做接口的抽象,其描述了一个对象"能"干什么,其中包含了对象拥有的方法(不再包含数据),接口隐藏了对象的大部分细节,使得对象变成一个黑箱,且展平了类结构(不再是树状),然而接口(这里指运行时接口而非泛型)作为一种非常高层次的抽象,这种抽象层次似乎有时会过高,导致CPU更难以理解代码,这点在稍后会讨论到。

类似的,在游戏开发中,面对大量的对象种类,大家又描述了一种组件的抽象,如 UE4 中的 Actor Component 模型和 Unity 中的 Entity Component 模型,其描述了一个对象"有"什么部分,其中对象本身不再拥有代码或数据(但其实 Unity 和 UE4 之类的并没有做到这么纯粹,对象本身依然带有大量"基础"功能,这导致了代码量和内存占用的双重膨胀)。组件的方式带来了优越的动态性,对象的状态完全由其拥有的组件决定(同样,一般没这么纯粹),甚至可以动态的改变。并且这让我们可以排列组合以少量的组件组合出巨量的对象(当然,有效组合往往没那么多)。有趣的是,从展平对象结构的角度看起来和组件和接口有着微妙的相似性。不过这种抽象也带来出了一些歧义性,接下来将讨论这一点。

64b4e01e9ca93a6d927d289dd5ba65a4.png
组合

2. “有”和”能”和实现

在组件模型中,对象由组件组成,所以其行为也由组件主导,例如一个对象拥有[Movement] 和 [Location],则我们可以认为它能够移动,这在整体上是十分和谐自然的,但当我们仔细考量,这个"能"是由于什么呢,是因为 [Movement]吗,是因为[Location]吗,还是同时因为 [Movement] 和 [Location]?当然是同时(这里便揭示出了组件和接口的展平对象方式是正交的),那移动的逻辑放到哪呢?答案是放在这个“切片“上。但在实际项目中会看到把逻辑放在 [Movement] 上的做法,这两种方式都是可取的,后一种拥有较为简单的实现并被广泛采用,而前一种拥有更精准的语义,更好的抽象(后一种种方式中 [Movement] 去访问并修改了 [Location] 的数据,这破坏了一定的封闭性,且形成了耦合,当然这种耦合也有一定的好处,如避免只添加了 [Movement] 这种无意义的情况发生)。


从Cache说起

  1. Cache Miss

Cache(Cache Memory)作为储存器子系统的组成部分,存放着程序经常使用的指令和数据,是为了缓解访问慢设备的延时而预读的 Buffer,例如 CPU L1/L2/L3 Cache 作为 DDR 内存 IO 的 Cache,而 DDR 内存作为磁盘 IO 的 Cache。当计算需要读取数据的时候,通常从最快得缓存开始依次向下查找,并递归的读取。预读就是用来减少下一次读取的查找层数(每一层的延迟有数量级的差距)的技术。相应的,预读的预测失败的时候将会有非常高的代价,这种情况被称为 Cache Miss。在大部分的情况下,在现代 CPU 的频率带来的运算力下, Cache Miss 比数学运算更容易成为程序的性能瓶颈,且在代码中的表现比较隐晦。这使得一味的讨论复杂度O(n)不再适用,因为现在效率=数据+代码,最常见的例子就是在数据量小的情况下遍历数组会比 (Hash)Map 快上很多,这也是Java或C#这类语言的效率陷阱.。

42cea53e1de2672b8e54930d490318c4.png
从上到下进行查找

2. Avoid Cache Miss

避免 Cache Miss 的方案当然就是去讨好预读。而一般预读的策略为线性预读,即我们应该尽量的保证数据读写的连续性,从逆向思维出发,则需了解会打断数据连续性的情形。简单的列举几个:遍历大结构体的数组(却只访问少数成员),操作对象引用(OOP),操作数组的顺序不够连续(比如实现得不好的 hash 表),etc。综上所述,避免Cache Miss的主要考量就是尽量使用数组,尽量分割属性(SOA),尽量连续的进行处理。(在 GPU 编程中存在大量实例)

9d698fd635057f136f2f844f52db3ca1.png
此时达到理论最高效率

3. More than Data

前面提到过 Cache 存放着程序经常使用的指令和数据,现代 CPU 在数据 IO 的时候并不会完全的挂起,而是会利用空闲的运算力继续执行后续的指令,且指令也是一种数据,这意味着我们不光要照顾数据的连续性,还需要考虑到指令的连续性,那么什么情况会破坏指令的连续性呢?可能是函数指针(虚函数的调用,回调等),循环超长代码块等。特别是函数指针在 IO 期间,CPU 无事可做,于是在需要高性能的情形下,应该尽量避免虚函数

4. Allocation

对于数据而言,还有一个重要的问题就是分配内存。在应用中,不管是分配还是释放都是十分消耗性能的操作,前者可能产生碎片,而后者,(考虑 GC)可能带来停顿,(考虑 RC)带来析构血崩,(考虑手动)也可能带来危险和脑力负担,所以一般对于高频分配的部分,会预先分配大块内存用来管理(一般称作池化)。


从 Thread 说起

  1. Multithread

随着处理器核心的发展速度减缓,为了进一步提升处理器的性能,堆叠核心成为了新的出路,甚至现在的处理器没个四核都不好意思见人,其中堆叠核心的巅峰就是 GPU,上千个核心带来了疯狂的数字处理能力,被广泛运用于 AI 和图形领域。而这在游戏之类的高性能软件中,为了充分利用 CPU 的算力,程序设计成多线程运行也是非常必要的。

2. Race Condition 和 Data Race

不幸的是多线程很多时候不是免费的性能,并不是所有情况都像异步读文件那么简单,在开发过程中,很多地方都可能会有 Race 的发生。同步性问题非常的恶心,因为通常其不会即时造成崩溃之类的错误,而是会积累错误,等到错误爆发,缘由已经很难查询。所以编码的时候就必须要小心翼翼,其中 Race Condition 主要需要我们保证整体操作的原子性,一般的解决方案是一把大锁。Data Race 则更加复杂,触发Data Race的条件可以归纳为:

1,同一个位置的对象。

2,被两个并行的线程操作。

3,两个线程并非都是读。

4,不是原子操作。

1134d837845c9151c61acb09e21eee75.png

只有当这四个条件同时成立的时候,Data Race 才会发生,所以为了避免它的发生,我们需要破坏掉其中的一个或多个条件。对于条件4,可以使用原子操作破坏,然而原子操作的复杂性颇高,实际应用中常用于实现底层库(无锁队列,线程池之类的)。而要破坏条件1、3,则是避免可变共享,完全进行拷贝(如erlang)。剩下条件2就是避免硬碰硬,在可能发生 Data Race 的时候直接放弃并行。但总得来说最重要的还是,要避免它的发生,一定要对这些条件足够敏感以预防遗漏,在这里通常封装就起了反作用,因为黑箱之内我们无法知道会发生什么。而此时相对于 OOP 的黑箱,函数式的纯粹(纯原子性)便能体现出它在并行上天生的优势,所以卡神推荐在 C++ 里也尽量使用函数式的思想来进行编码


交汇之地 - 三相之力!

  1. Component 与 System

之前说到组件模式的时候,我们列举了两种方式来存放实现组件功能的代码,而使用“管理器”实现的方式,拥有更精准的语义和更好的抽象,组件之间被彻底解耦,而这个“管理器”我们称之为系统(System)。即系统负责管理特定的组件的组合,而组件则不再负责逻辑。接下来分别讨论这两个部分。

825c9113e0538a37ea208768a5c9e579.png
筛选对应的实体

2. System

对象耦合于接口,而这里系统则耦合于对象。这意味着组件不变的情况下,系统的任何修改都不会对程序的其余部分造成影响。这给代码带来了出色的内聚性,让 culling 和 plugin 都变得更轻松,并且系统本身拥有很好的纯度,我们完全可以把系统看做是”输入上一帧的数据,输出下一帧的数据“。也就是系统本身贴合了函数式的思想,根据前面的叙述,函数式在并行上有天生的优势,这在系统上也体现了出来:系统负责管理组件的信息是透明的,于是我们对系统对组件的读写便一目了然 - 注意结构体之间没有任何依赖,系统与系统之间的冲突也一目了然。更进一步,在通常情况下,系统是一个白箱,运作系统的代码将不会经过虚函数,不管是效率还是可测试性都是极好的。甚至对于系统的执行调度也完全暴露了出来,这在实现网络同步之类的框架的时候能提供很大的便捷性。

3. Component 与 Entity

对于对象本身,其实已经不必要承载多少信息了,激进一点说,对象甚至只是一个唯一的ID,用于和其他对象区分而已,这让我们有机会去除那些"基础"功能的依赖(例如 Transform),使得内存和代码进一步压缩。而组件不包含逻辑,就只有数据,作为一个大的对象的分割的属性,通常为小结构体。对于每一种组件,我们可以使用紧密的数组来储存它,而这也意味着我们可以轻松的池化这个数组。在系统管理组件的时候,并不关心特定 Entity,而是在组件数据的切片上批量的连续的进行处理,这在理想情况下能大大的减少 Cache Miss 的情况。作为额外的好处,纯数据的组件对序列化,表格化有着极强的适应性,毕竟对象天生就是一个填着组件的表格,对网络、编辑、存档等都十分的友好。(这里也可以引入很多数据库相关的知识)

4. ECS

至此,我们重新发现了 ECS,并详细阐述了它的好处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/560039.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

条令考试小程序辅助器_计算机一级考试干货!

计算机一级考试干货一年两度的计算机等级考试就要在2019年3月30-31日期间开始啦!为了更好的让同学们了解考试的大体内容,我们已整理如下内容,可以供大家参考。同时,希望大家认真备考,争取都一次性过哦!考试…

燃气灶电气线路图及原理_一位造价大神的电气工程造价知识整理笔记_深圳电气造价预算培训要多少钱...

电气设备安装工程是建设工程中的一种常见的、重要的设备安装工程。电气设备安装工程计价涉及到许多电气工程专业知识。一、电气设备安装工程的组成一般的电气设备安装工程是以接受电能,经变换、分配电能,到使用电能或从接受电能经过分配到用电设备所形成…

荣耀es升级鸿蒙,华为手机明年全部升级鸿蒙系统 所有自研设备换OS

近日,华为举行 HarmonyOS 2.0 手机开发者 Beta 活动,现场正式发布了 HarmonyOS 2.0 手机开发者 Beta 版本。华为消费者业务软件部副总裁杨海松在接受媒体采访表示,到今天为止,参与鸿蒙开发项目的开发者数量超过 10 万,…

腐蚀国内稳定服务器_工控机箱和服务器机箱区别在哪里

服务器机箱必须能够装进机柜,一个标准机柜的宽度是19英寸482.6mm,所以机箱的宽度是几乎固定的,一般是17英寸左右,两侧再安装把手和轨道。机箱高度也有规定,用U(Unit)做单位,1U是44.5mm,机箱高度…

limit实现原理 mysql_解读数据库:深入分析MySQL中事务以及MVCC的实现原理

什么是事务事务(Transaction)是由一系列对数据库中的数据进行访问与更新的操作所组成的一个程序执行单元。在同一个事务中所进行的操作,要么都成功,要么就什么都不做。理想中的事务必须满足四大特性,这就是大名鼎鼎的ACID。事务的…

c# 审批流引擎_小熊OA:流程引擎才能真正起到管理价值!

首先说说什么是流程管理。流程作为企业运作的基础,不同部门、不同客户和供应商都需要流程来进行协同运作,以流程带动信息、物资和资金在企业内部无障碍地流转。流程管理是一种以业务流程为中心,以提高组织业务绩效为目的的系统化方法。它是一…

在计算机桌面上添加小工具日历,实用桌面小工具时钟日历在win7中的添加方法...

我们在win7系统的使用中,小伙伴们都是知道的系统可以直接选择安装不同的小工具在电脑中使用,比如电脑中的时钟日历等都是可以直接安装在桌面来使用的牡丹石有小伙伴对于时钟日历不知道是在哪里添加到桌面的,对于这个疑问今天小编就来跟大家分…

html 为什么ul不撑开,给li设置float浮动属性之后,无法撑开外层ul的问题。

以下面代码为例,其实有好几种解决方法,我用的这种并不是最简单的。给li设置float浮动属性之后,无法撑开外层ul的问题。ul{border: 1px solid #000;width: 200px;height: auto;margin-top: 100px;}li{float: left;list-style: none;margin-lef…

计算机黑屏无法启动,电脑黑屏无法启动怎么办

有网友说自己的电脑黑屏无法启动,具体现象就是开机以后,主机电源灯亮,但显示没有任何显示。那么电脑黑屏无法启动的原因很多,下面小编就给大家分享下电脑不能开机黑屏的解决方法。静电影响:1、当出现不能开机的问题时&…

测试显卡的软件叫游戏什么,显卡测试软件哪个好

显卡测试软件哪个好3DMARK063DMark06于2006年1月17日发布,主要使用最新一代游戏技术衡量DirectX 9级别的3D硬件。此前的3DMark都是随着新版DirectX和新一代硬件的发布而推出,在一定程度上限制了3DMark对最新硬件性能的充分挖掘。现在,DirectX…

华润置地php面试题_长春华润置地崑崙御,以至臻匠心,成就城市典范

点击上方蓝字,记得关注我们!长春华润置地崑崙御,以至臻匠心,成就城市典范每一次启程都是为了更好的遇见,每一次出发都是为了更高的追求。6月23日,集精工匠心所筑,汇万千美好所成,华润置地崑崙御…

数据消费过程_特色鲜卤现捞加盟店经营要如何抓准消费群体定位

唐小卤是市场上摸爬滚打了这么多年,认为对市场来说,有成功的创业者标明,在目前的行业中,谁能抓住女性消费者,谁就能占领更多的市场份额,诚然,女性消费者在市场里占领了重要地位,这在…

做形态学方法的团队_图像分割实战-分水岭分割方法和GrabCut 算法

1. 分水岭分割方法它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。风水岭算法常见的有三种方法:&…

计算机gt的使用方法,旗舰级综合效果器 BOSS GT-1000使用宝典(二) | 基础操作

原标题:旗舰级综合效果器 BOSS GT-1000使用宝典(二) | 基础操作在上一期的使用宝典中,B老板为大家介绍了GT-1000内置的前级,箱体及麦克风等,👉 GT-1000使用宝典(一) | 了解你的神器 想必各位对自己手中的神器有了比较全…

日期格式化为yyyymmdd_你还在用SimpleDateFormat格式化时间嘛

Jdk1.8之时间处理该文章已经同步到Github:https://github.com/stackInk/makerstack1. 传统时间处理的问题1.1 多线程环境下的SimpleDateFormat当多个线程使用同一个时间处理对象进行对日期的格式化的时候,会出现java.lang.NumberFormatException: multip…

存储ic载板_延伸IC领域 崇达技术拟将持有普诺威55%股权

立即加星标每天看好文PCB网城讯崇达技术7月1日公告,6月30日,崇达技术股份有限公司(以下简称“崇达技术”)与朱小红、马洪伟在公司会议室签署了《关于江苏普诺威电子股份有限公司之股份转让协议》(以下简称“协议”)。根据协议,公司拟以自有资…

微云服务器失败原因_梦幻西游:服务器发生异常?游戏出现明显卡顿感,正在排查问题...

就在刚刚,不少梦幻玩家都在讨论一个情况,那就是服务器出现了明显的卡顿感,一些商人也陆续掉线,难道是服务器出现了异常?退出游戏之后,一直无法进入,登录界面总是停留在"正在连接某某服务器…

maven多模块项目部署到服务器,GitHub - baxias/foweb: 一个基于 Spring+SpringMVC+Mybatis 的Maven多模块项目。(实现前后端分离的服务器端)...

Foweb FrameworkA multi-modules maven project base on SpringSpringMVCMybatis.一个基于 SpringSpringMVCMybatis 的Maven多模块项目。使用文档两种使用方式:1. 直接将项目download下来,然后在IDE(Eclipse或者IDEA)中以maven项目导入,注意这…

网站需要数据库服务器吗,网站需要独立的服务器数据库吗

网站需要独立的服务器数据库吗 内容精选换一换文档数据库服务提供使用数据管理服务(Data Admin Service,简称DAS)、内网和公网的连接方式。文档数据库服务默认为您开通了远程主机登录权限,推荐您使用更安全便捷的数据管理服务连接实例,具体请…