Unity内部:内存和性能(以及Unity5的升级优化)

发表于2017-10-17
评论0 2.1k浏览
我们的脚本代码里经常会需要访问gameObject引用或者某个组件的引用,最好的方式当然是在脚本Awake的时候就把这些可能访问的东西都缓存下来;如果需要访问临时gameObject实例的某属性或者临时某组件的gameObject实例,在能够确保组件一定存在(可以使用[RequireComponent( typeof(AudioSource ))] 如果没有自动添加移除不了!)的情况下,可以用属性访问,毕竟属性访问比GetComponent要快上一倍,但是如果不能确定组件是否存在,甚至是需要对组件的存在性做判断时,一定不要用对属性访问结果判空的方式,而要用GetComponent,这里面节省的开销不是一点半点。

而在unity5中你是没得选择了,因为没有了属性访问器。 看看下图的 GameObject类的对比。(4.6和5.0版本)

     

unity内部:内存 和 性能
Topics
• Memory Overview
• Garbage Collection
• Mesh Internals
• Scripting
• Job System
一、 Memory Overview
Memory Domains内存域
• Native (internal)本地 (内部)
• Asset Data: Textures, AudioClips, Meshes
• Game Objects & Components: Transform, etc..
• Engine Internals: Managers, Rendering, Physics, etc..
• Managed - Mono
• Script objects (Managed dlls)
• Wrappers (包装类)for Unity objects: Game objects, assets,components
• Native Dlls
• User’s dlls and external dlls (for example: DirectX)

Native Memory: Internal Allocators本机内存: 内部分配器
• Default
• GameObject
• Gfx 图形和渲染相关
• Profiler事件探查器
               5.x: 在 Dll 中使用本机的分配器公开的 API

Managed Memory托管的内存
• Value types值类型 (bool, int, float, struct, ...)
          • 在堆栈内存中存在。当从堆栈中移除时取消分配。没有GC。
• Reference types (classes) 引用类型 (类)
• 在堆上存在,当长时间不再被引用时都由mono/.net GC删除。
• 包装为 Unity Objects :
• GameObject
• Assets : Texture2D, AudioClip, Mesh, …
• Components : MeshRenderer, Transform, MonoBehaviour
Mono Memory Internals
• Allocates system heap blocks for internal allocator(为内部分配器分配系统堆块)
• Will allocate new heap blocks when needed( 在需要时将分配新的堆块)
• Heap blocks are kept in Mono for later use( 堆块保持在Mono中以后使用)
• Memory can be given back to the system after a while( 当过一段时间内存可以被交还给系统)
•......但是它取决于平台è ,别指望它
• Garbage collector cleans up(垃圾回收器清理)
• Fragmentation can cause new heap blocks even though memory is not exhausted(碎片可以导致新的堆块,即使内存不耗尽)

二、Garbage Collection
Unity Object wrapper(封装)
• Some Objects used in scripts have large native backing memory in unity(在unity中在脚本中被使用的一些对象有大批的本机支持内存)
• 直到运行Finalizers终结器内存才会释放

Mono Garbage Collection
•  GC.Collect
• Runs on the main thread when(运行在主线程上当:
• Mono exhausts the heap space( Mono耗尽堆空间)
• Or user calls System.GC.Collect()(或用户调用 System.GC.Collect())
•  Finalizers终结器
     • Run on a separate thread(运行在一个单独的线程上当:
• Controlled by mono(由mono控制
• Can have several seconds delay(可以有几秒钟的延迟
•  Unity native memory(unity本机内存
• Dispose() cleans up internal memory(Dispose () 清理内部内存
• Eventually called from finalizer( 最终从finalizer终结器调用
• Manually call Dispose() to cleanup(手动调用 Dispose() 清理
Garbage Collection
• Roots are not collected in a GC.Collect(在GC.Collect上Roots不会被收集)
• Thread stacks(线程堆栈)
• CPU Registers(CPU 寄存器)
• GC Handles (used by Unity to hold onto managed objects) GC 句柄 (使用Unity来守住托管对象)
• 静态变量 !!
• Collection的时间尺度与托管的堆的大小
• 您分配的越多,gets变得越慢

GC: Best Practices 最佳做法
• Reuse objects . Use object pools(重用对象,使用对象池)
• 更喜欢基于堆栈的分配。使用结构而不是类
• System.GC.Collect 可以用于触发collection
• 手动调用 Dispose立即清理

Avoid temp allocations避免临时分配
• Don’t use FindObjects or LINQ
• Use StringBuilder for string concatenation
• Reuse large temporary work buffers(重用大量临时工作缓冲区)
• ToString()
• .tag 改用 CompareTag() 方法

Unity API Temporary Allocations(临时分配)
一些例子:
• GetComponents<T>
• Vector3[] Mesh.vertices
• Camera[] Camera.allCameras
• foreach
•  does not allocate by definition(定义不会分配)
•  However, there can be a small allocation, depending on the implementation of .GetEnumerator()
• 然而,根据具体的.GetEnumerator()实现可能会有 small有小的分配
5.x: 正在研究新的非分配non-allocating版本

Memory fragmentation内存碎片
• Memory fragmentation is hard to account for(内存碎片很难解释)
• Fully unload dynamically allocated content(完全卸载动态分配内容)
• 切换到一个空白场景,在next level之前
• This scene could have a hook where you may pause the game long enough to sample if there is anything significant in memory
• Ensure you clear out variables so GC.Collect will remove as much as possible
• 确保你clear变量,GC.Collect将删除尽可能多地
• 尽可能避免分配
• 重用对象尽可能在scene play下
• Clear them out for map load to clean the memory(把它们清理干净memory的地图加载)

Unloading Unused Assets卸载未使用的资产
• Resources.UnloadUnusedAssets 将触发资产垃圾回收
•他是搜索所有unreferenced assets 和卸载他们
•他是一个async operation异步操作的 
• 它loading 一个 level后在内部被调用
• Resources.UnloadAsset 是最好的
• 您需要知道到底您需要Unload卸载什么
•Unity而不必全部扫描
• Unity 5.0: 多线程的asset垃圾回收

三、Mesh Internals
Mesh Read/Write Option
• 它允许您在运行时修改mesh
• 如果启用enabled, Mesh的系统副本会保留在内存中
• enabled是默认
• 在某些情况下,禁用此选项不会减少内存使用量
• Skinned meshes皮肤网格
• iOS
Unity 5.0: disable by default默认情况下是禁用 – under consideration

Non-Uniform scaled Meshes非均匀缩放网格
我们需要正确转换的顶点法线
• Unity 4.x:
• 在 CPU 上变换transform网格mesh
• 创建数据的额外副本
• Unity 5.0
• 在 GPU 上缩放Scaled
• 不再需要额外的内存
Static Batching
这是什么?
• 它是优化,减少了draw calls 和状态改变的数量
他如何使用?
• 在player settings   Tag对象为static。如下:

它内部如何工作?
• Build-time 生成时间: Vertices are transformed to world- space顶点转换为世界空间
• Run-time运行时间: Index buffer is created with indices of visible objects索引缓冲区创建与指数的可见对象
Unity 5.0:
• Re-implemented static batching without copying of index buffers重新实施静态配料而不复制索引缓冲区
Dynamic Batching
这是什么?
• 类似于静态配料,在运行时对象是non-static
他如何使用?
• 在player settings
• no need to tag. it auto-magically works…无需标记。它自动神奇地工作......
它内部如何工作?
• objects are transformed to world space on the对象转换到世界空间上
CPU
• 创建临时的 VB & IB
• 在一个draw call 中呈现Rendered 
Unity 5.x:  我们正在考虑使每个平台参数

Mesh Skinning
根据平台的不同实现:
• x86: SSE
• iOS/Android/WP8: Neon optimizations
• D3D11/XBoxOne/GLES3.0: GPU
• XBox360, WiiU: GPU (memexport)
• PS3: SPU
• WiiU: GPU w/ stream out
Unity 5.0: Skinned meshes 通过共享实例之间的索引缓冲区使用较少的内存

四、Scripting(重要!)
Unity 5.0: Mono
• No upgrade不能升级
• Mainly bug fixes主要 bug 修复
• New tech in WebGL: IL2CPP新科技在 WebGL: IL2CPP
• http://blogs.unity3d.com/2014/04/29/on-the-future-of-web-publishing-in-unity/
• 敬请: 会有关于它的博客文章
GetComponent<T>       
它要求游戏物体,得到一个指定类型的组件:
• The GO contains a list of Components
• GO包含Components组件的列表
• 每个组件类型被比作 T
• 第一个组件的类型 T (或从 T 派生),将返回给调用方
• 不太多的开销,但它仍然需要调入本机代码
Unity 5.0: Property Accessors属性的访问器
• 大多数accessors将在Unity 5.0中被移除
• 目的是减少依赖,因此提高模块化
• Transform将仍然被保留
• 现有的脚本将被转换。示例:
in 5.0:

Transform Component
• this.transform 是和 GetComponent<Transform>() 一样
•  transform.position/rotation 需要:
• find Transform component
• Traverse hierarchy to calculate absolute position遍历层次结构来计算绝对位置
• Apply translation/rotation
•  transform internally stores the position relative to the parent变换在内部存储的位置是相对于父级
• transform.localPosition = new Vector(…) →  简单的赋值
• transform.position = new Vector(…)→  costs the same if no father, otherwise it will need to traverse the hierarchy up to transform the abs position into local
• transform.position = 新的 Vector(...)→  如果没有父亲代价是一样的,否则它将需要本地遍历hierarchy得到transform的位置绝对值
• 最后,将通过messages通知其他组件 (对撞机、 刚体、 轻型、 相机、)。

Instantiate
API:
• Object Instantiate(Object, Vector3, Quaternion);
• Object Instantiate(Object);
Implementation执行:
• 克隆GameObject Hierarchy and Components
• Copy Properties复制属性
• Awake
• Apply new Transform (if provided)适用新的变换 (如果提供)
Instantiate cont..ed
• Awake 可以是昂贵的
• AwakeFromLoad (主线程)
• clear states清理状态
• internal state caching内部状态缓存
• pre-compute预先计算
Unity 5.0:
• Allocations have been reduced分配已被减少
• 一些内部的循环,用于复制的数据进行了优化
JIT Compilation(JIT 编译)
这是什么?
• 在进程中从 CIL 代码生成机器代码,在应用程序运行期间
Pros:优点:
• 为当前平台,它生成优化的代码
Cons:缺点:
•  Each time a method is called for the first time, the application will suffer a certain performance penalty because of the compilation
• 每次方法被第一次调用时,应用程序因为汇编将受到某些性能处罚 
JIT compilation spikes
pre-JITting怎么样?
• RuntimeHelpers.PrepareMethod 是行不通的:

....MethodHandle.GetFunctionPointer() 比较好

五、Job System
Unity 5.0: Job System (internal)
job system的目标:
• 基于多线程的代码 可以很容易地写得很高效率的工作
• The jobs should be able to run safely in parallel to script code
• jobs工作应该能够安全地平行的运行脚本代码
Job System: 它是什么 ?
• 它是一个Framework框架,我们打算在现有的和新的子系统中使用的
• 我们想要Animation, NavMesh, Occlusion, Rendering,等等尽可能多的并行运行
• 这最终将导致更好的性能

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引