Unity 内存管理

发表于2017-03-15
评论0 6.5k浏览

Unity 内存管理

为什么在unity项目开发中的内存占用是一个非常大的问题,那是因为一些场景和代码造成的非必要内存的占用,那么这种问题如何解决呢?下面会为大家unity中的内存管理了解内存的种类以及对应种类的优化和使用的技巧。

三种内存

·                     程序代码

·                     托管堆(Managed Heap)

·                     本机堆(Native Heap)

程序代码就是Unity引擎本身以及我们使用的各种库,还有自己写的程序代码,这些代码段是要载入内存才能使用的

我们要尽量减少不必要的库被打包进最终的包里,具体可以看Optimizing the Size of the BuiltiOS Player

托管堆就是C#/Mono代码使用的内存。托管堆是有GC(Garbage Collect)机制的,这使得我们不需要手动delete任何实例
需要注意的是,这一块本质上一个被虚拟机管理的内存,在我们创建Class的实例时,如果虚拟机空间不足,是会申请新的空间的,但是,这个空间
不会再被释放回去就是说,托管堆只会增长不会减少,即使你的C#类的实例都被GC掉了,但虚拟机还是要占那么多的内存的

iOS中,为了提供64的支持,Unity开启了IL2CPP项目,但是这个东西也还是一个虚拟机,我们的C#代码虽然被翻译成了C++代码,但它的内存管理逻辑和C#还是一样的

托管堆还有一个坑是GCGC会造成线程停顿,主线程需要停下来来完成GC操作。所以,在战斗过程中,要尽量避免Managed Heap的请求和释放(主要是释放)

C#提供了值语义来实现简单的类C++RAII的概念,可以避免频繁的GC。但要注意box/unbox问题

本机堆可以是Unity自己管理的一部分内存,主要是一些Native的资源,比如贴图,音效,关卡数据等。这部分内存我们也可以自己申请和使用的,我们使用native插件也是使用这部分内存。

C#代码中,我们也可以申请和使用Native Heap的:

1
2
int size = 65536;//1024;
      byte* buffer = (byte*)Marshal.AllocHGlobal(size);

Marshal.aspx)C#提供的一个直接操作Native内存的接口,通过这个系统,我们可以绕过GC系统,手动管理内存,以在某些情况下大幅优化C#的性能

Unity中,有一套自动的资源加载和卸载的机制,这使我们不需要在代码中指定任何资源的加载过程,我们想要显示的时候,它就会在哪里了,非常方便。但是,这样我们也失去了手动管理资源的权力,很容易导致Unity吃掉大量的内存

UnityAsset使用了一套引用计数的系统来管理

当我们使用UnityProfiler来观察内存占用情况时,我们可以看到每个Asset后面会有一个
Refcount的数字,而右边列出引用了它的东西,这些一般是具体的C#实例

如果一个Asset没有了引用,使用Resources.UnloadUnusedAssets()就可以卸载它了。使用Resource.UnloadAsset()可以强制卸载一个资源,但是如果它还有引用,它会被再次加载进来的

Unity默认会在切换Scene时自动卸载所有资源,因为所有的C#实例都被销毁掉了

我们要在一个Scene中动态的加载和卸载资源,就要用Resource.UnloadAsset()了。尽量不要使用Resources.UnloadUnusedAssets()因为它是一个遍历查找的操作,会造成卡顿

AssetBundle加载

AssetBundleUnity提供的唯一的资源更新方法,非常有用

它的整个操作周期可以使用下图来表示:

Instantiate指的是PrefabGameObject.Instantiate, 这个函数会实例化一个Prefab,这个Prefab所使用的Asset会根据不同的类型,进行引用Clone或者Clone(复制)+引用的形式来产生

AssetBundle.Load就是一个反序列的过程,它会创建一个GameObject出来,这个GameObject可以用来实例化成我们真正可用,挂载在场景树中的GameObejct

AssetBundle压缩

注意上图的紫色的AssetBundleMemory部分

一般来说,我们要使用LoadFromCacheOrDownload来加载AssetBundle因为这样可以避免这部分内存占用。但是,Unity其实是把AssetBundle解压到一个Cache里然后加载的,还是会增加本地的磁盘占用的,在iOS中,这个占用还是比较麻烦的。因为LZMA压缩可以节省80%左右的磁盘占用,这意味着,解压后会放大五六倍

Unity5.3中,引入了一个新的压缩方式LZ4,它是一个chunk-based(基于块)的压缩方式,它的好处就是,在读区时我们可以只解压指定的chunks而不必如LZMA一样,解压整个AssetBundle文件到内存

LZ4可以节省40%60%的空间,同时实时读取解压时的性能消耗还可以接受

可以看到,LZ4版本的AssetBundle中加载prefab性能下降还是很明显的

需要注意的是,在5.3后的版本中,WWW.LoadFromCacheOrDownload默认产生的Cache,都是使用LZ4压缩的,而不是完全解压后的版本。如果你注意到AssetBundle中加载资源比Resources中慢,检查一下这个

使用LZ4可以解决iOS中,游戏占用过多磁盘空间的问题,同时带来的性能消耗还可以接受。
使用
LZ4我们就可以直接使用LoadFromFile来加载AssetBundle了,而且不必调用AssetBundle.Unload(false)来释放内存空间,也不必卸载AssetBundle本身了。AssetBundle甚至可以被看作一个简单的虚拟文件系统了,只要挂上就可以了

引用:

·                     Unity Internals: Memory andPerformance

·                     Asset Bundle Compression

 

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