首页 > 系统相关 >Unity内存优化(来自uwa)

Unity内存优化(来自uwa)

时间:2024-05-20 10:52:32浏览次数:29  
标签:Mono 纹理 Unity AssetBundle 内存 uwa 分配 资源

内存优化——

“勿以善小而不为,勿以恶小而为之”
  资源内存占用 一、纹理资源 纹理资源可以说是几乎所有游戏项目中占据最大内存开销的资源。一个6万面片的场景,网格资源最大才不过10MB,但一个2048x2048的纹理,可能直接就达到16MB。因此,项目中纹理资源的使用是否得当会极大地影响项目的内存占用。 那么,纹理资源在使用时应该注意哪些地方呢?   (1) 纹理格式 纹理格式是研发团队最需要关注的纹理属性。因为它不仅影响着纹理的内存占用,同时还决定了纹理的加载效率。一般来说,我们建议开发团队尽可能根据硬件的种类选择硬件支持的纹理格式,比如Android平台的ETC、iOS平台的PVRTC、Windows PC上的DXT等等。 在使用硬件支持的纹理格式时,你可能会遇到以下几个问题: 色阶问题 由于ETC、PVRTC等格式均为有损压缩,因此,当纹理色差范围跨度较大时,均不可避免地造成不同程度的“阶梯”状的色阶问题。因此,很多研发团队使用RGBA32/ARGB32格式来实现更好的效果。但是,这种做法将造成很大的内存占用。比如,同样一张1024x1024的纹理,如果不开启Mipmap,并且为PVRTC格式,则其内存占用为512KB,而如果转换为RGBA32位,则很可能占用达到4MB。所以,研发团队在使用RGBA32或ARGB32格式的纹理时,一定要慎重考虑,更为明智的选择是尽量减少纹理的色差范围,使其尽可能使用硬件支持的压缩格式进行储存。 ETC1 不支持透明通道问题 在Android平台上,对于使用OpenGL ES 2.0的设备,其纹理格式仅能支持ETC1格式,该格式有个较为严重的问题,即不支持Alpha透明通道,使得透明贴图无法直接通过ETC1格式来进行储存。对此,我们建议研发团队将透明贴图尽可能分拆成两张,即一张RGB24位纹理记录原始纹理的颜色部分和一张Alpha8纹理记录原始纹理的透明通道部分。然后,将这两张贴图分别转化为ETC1格式的纹理,并通过特定的Shader来进行渲染,从而来达到支持透明贴图的效果。该种方法不仅可以极大程度上逼近RGBA透明贴图的渲染效果,同时还可以降低纹理的内存占用,是我们非常推荐的使用方式。 (2)纹理尺寸 一般来说,纹理尺寸越大,则内存占用越大。所以,尽可能降低纹理尺寸,如果512x512的纹理对于显示效果已经够用,那么就不要使用1024x1024的纹理,因为后者的内存占用是前者的四倍。 (3) Mipmap功能 Mipmap旨在有效降低渲染带宽的压力,提升游戏的渲染效率。但是,开启Mipmap会将纹理内存提升1.33倍。对于具有较大纵深感的3D游戏来说,3D场景模型和角色我们一般是建议开启Mipmap功能的,但是在我们的测评项目中,经常会发现部分UI纹理也开启了Mipmap功能。这其实就没有必要的,绝大多数UI均是渲染在屏幕最上层,开启Mipmap并不会提升渲染效率,反倒会增加无谓的内存占用。 (4) Read & Write 一般情况下,纹理资源的“Read & Write”功能在Unity引擎中是默认关闭的。但是,我们仍然在项目深度优化时发现了不少项目的纹理资源会开启该选项。对此,我们建议研发团队密切关注纹理资源中该选项的使用,因为开启该选项将会使纹理内存增大一倍。 二、网格 网格资源在较为复杂的游戏中,往往占据较高的内存。       引擎模块自身占用 引擎自身中存在内存开销的部分纷繁复杂,可以说是由巨量的“微小”内存所累积起来的,比如GameObject及其各种Component(最大量的Component应该算是Transform了)、ParticleSystem、MonoScript以及各种各样的模块Manager(SceneManager、CanvasManager、PersistentManager等)...   上面所指出的引擎各组成部分的内存开销均比较小,真正占据较大内存开销的是这两处:WebStream 和 SerializedFile。 其绝大部分的内存分配则是由AssetBundle加载资源所致。简单言之,当您使用new WWW或CreateFromMemory来加载AssetBundle时,Unity引擎会加载原始数据到内存中并对其进行解压,而WebStream的大小则是AssetBundle原始文件大小 + 解压后的数据大小 + DecompressionBuffer(0.5MB)。   当项目中存在通过new WWW加载多个AssetBundle文件,且AssetBundle又无法及时释放时,WebStream的内存可能会很大,这是研发团队需要时刻关注的。   对于SerializedFile,则是当你使用LoadFromCacheOrDownload、CreateFromFile或new WWW本地AssetBundle文件时产生的序列化文件。   对于WebStream和SerializedFile,你需要关注以下两点:
  • 是否存在AssetBundle没有被清理干净的情况。开发团队可以通过Unity Profiler直接查看其使用具体的使用情况,并确定Take Sample时AssetBundle的存在是否合理;
  • 对于占用WebStream较大的AssetBundle文件(如UI Atlas相关的AssetBundle文件等),建议使用LoadFromCacheOrDownLoad或CreateFromFile来进行替换,即将解压后的AssetBundle数据存储于本地Cache中进行使用。这种做法非常适合于内存特别吃紧的项目,即通过本地的磁盘空间来换取内存空间。
  托管堆内存占用 对于目前绝大多数基于Unity引擎开发的项目而言,其托管堆内存是由Mono分配和管理的。“托管” 的本意是Mono可以自动地改变堆的大小来适应你所需要的内存,并且适时地调用垃圾回收(Garbage Collection)操作来释放已经不需要的内存,从而降低开发人员在代码内存管理方面的门槛。 但是这并不意味着研发团队可以在代码中肆无忌惮地开辟托管堆内存,因为目前Unity所使用的Mono版本存在一个很严重的问题,即:Mono的堆内存一旦分配,就不会返还给系统。这意味着Mono的堆内存是只升不降的。   项目运行时,在场景A中开辟了60MB的托管堆内存,而到下一场景B时,只需要使用20MB的托管堆内存,那么Mono中将会存在40MB空闲的堆内存,且不会返还给系统。这是我们非常不愿意看到的现象,因为对于游戏(特别是移动游戏)来说,内存的占用可谓是寸土寸金的,让Mono毫无必要地锁住大量的内存,是一件非常浪费的事情。   不必要的堆内存分配主要来自于以下几个方面: 1.高频率地 New Class/Container/Array等。研发团队切记不要在Update、FixUpdate或较高调用频率的函数中开辟堆内存,这会对你的项目内存和性能均造成非常大的伤害。做个简单的计算,假设你的项目中某一函数每一帧只分配100B的堆内存,帧率是1秒30帧,那么1秒钟游戏的堆内存分配则是3KB,1分钟的堆内存分配就是180KB,10分钟后就已经分配了1.8MB。如果你有10个这样的函数,那么10分钟后,堆内存的分配就是18MB,这期间,它可能会造成Mono的堆内存峰值升高,同时又可能引起了多次GC的调用。在我们的测评项目中,一个函数在10分钟内分配上百MB的情况比比皆是,有时候甚至会分配上GB的堆内存。 2.Log输出。我们发现在大量的项目中,仍然存在大量Log输出的情况。建议研发团队对自身Log的输出进行严格的控制,仅保留关键Log,以避免不必要的堆内存分配。 3.UIPanel.LateUpdate。这是NGUI中CPU和堆内存开销最大的函数。它本身只是一个函数,但NGUI的大量使用使它逐渐成为了一个不可忽视规则。该函数的堆内存分配和自身CPU开销,其根源上是一致的,即是由UI网格的重建造成。 4.关于代码堆内存分配的注意点还有很多,比如String连接、部分引擎API(GetComponent)的使用等等     内存标准: 1.150MB总体内存标准 2.分配 纹理资源50M 网格资源20M 动画片段15M 音频片段15M Mono内存40M 其他10M   这里未包含较为复杂的字体文件及TextAsset  
内存泄露和资源冗余 造成内存不能完全回落的情况有很多,比如资源加载后常驻内存以备后续使用、Mono堆内存的只升不降等等,这些均可造成内存无法完全回落。一般来说,我们推荐的判断内存是否泄漏的方法如下: 一、检查资源的使用情况,特别是纹理、网格等资源的使用 资源泄漏是内存泄露的主要表现形式,其具体原因是用户对加载后的资源进行了储存(比如放到Container中),但在场景切换时并没有将其Remove或Clear,从而无论是引擎本身还是手动调用Resources.UnloadUnusedAssets等相关API均无法对其进行卸载,进而造成了资源泄露。   二、通过Profiler来检测WebStream或SerializedFile的使用情况 AssetBundle的管理不当也会造成一定的内存泄露,即上一场景中使用的AssetBundle在场景切换时没有被卸载掉,而被带入到了下一场场景中。对于这种情况,建议直接通过Profiler Memory中的Take Sample来对其进行检测,通过直接查看WebStream或SerializedFile中的AssetBundle名称,即可判断是否存在“泄露”情况。   三、通过Android PSS/iOS Instrument反馈的App线程内存来查看   无效的Mono堆内存开销 目前,Unity所使用的Mono版本中存在一个较大的问题,即内存一旦分配,则不会再返回给系统。这就衍生出另外一个问题—— 无效的Mono堆内存。它是Mono所分配的堆内存,但却没有被真正利用上,因此称之为“无效”。   如何避免或减少过多“无效堆内存”的分配 避免一次性堆内存的过大分配。Mono的堆内存也是“按需”逐步进行分配的。但如果一次性开辟过大堆内存,比如New一个较大Container、加载一个过大配置文件等,则势必会造成Mono的堆内存直接冲高,所以研发团队对堆内存的分配需要时刻注意 避免不必要的堆内存开销。     资源冗余 “资源冗余”,是指在某一时刻内存中存在两份甚至多份同样的资源。导致这种情况的出现主要有两种原因: 一、AssetBundle打包机制出现问题 同一份资源被打入到多份AssetBundle文件中。举个例子,同一张纹理被不同的NPC所使用,同时每个NPC被制作成独立的AssetBundle文件,那么在没有针对纹理进行依赖打包的前提下,就会出现该张纹理出现在不同的NPC AssetBundle文件中。当这些AssetBundle先后被加载到内存后,内存中即会出现纹理资源冗余的情况。对此,我们建议研发团队在发现资源冗余问题后,对相关AssetBundle的制作流程一定要进行检查。   二、资源的实例化所致 在Unity引擎中,当我们修改了一些特定GameObject的资源属性时,引擎会为该GameObject自动实例化一份资源供其使用,比如Material、Mesh等。 过多的冗余资源却为Resources.UnloadUnusedAssets API的调用效率增加了相当大的压力。      

标签:Mono,纹理,Unity,AssetBundle,内存,uwa,分配,资源
From: https://www.cnblogs.com/weigangblog/p/18201429

相关文章

  • Unity场景预加载
    async.allowSceneActivation=false;privateAsyncOperationasync;async=SceneManager.LoadSceneAsync("MainScene");async.allowSceneActivation=false;yieldreturnStartCoroutine(Loading());IEnumeratorLoading(){floatcurProgress......
  • Unity中Reorderable List用法(待维护)
    Unity官方文档里完全没有提到ReorderableList类,这是因为它不在UnityEngine或UnityEditor的命名空间下,而是在UnityEditorInternal命名空间下,这个命名空间里的东西是没有官方文档支持的 ReorderableList的作用它的作用,是让一个数组,在Unity的Inspector界面上显示得更好一些//......
  • Invalid URI at UnityEngineInternal.WebRequestUtils.MakeInitialUrl (System.Stri
    问题背景:有一个项目用到3d模型,原来访问地址用的是域名,访问老是报跨域问题,于是换成了内网地址这么一换问题来了,控制台直接报错 FormatException:InvalidURIatUnityEngineInternal.WebRequestUtils.MakeInitialUrl(System.StringtargetUrl,System.StringlocalUrl)[0......
  • Unity是如何跨平台的
    做游戏肯定要跨平台,Unity是如何实现跨平台的,这就得提到Unity脚本后端处理(ScriptingBackend)的两种方式Mono和IL2Cpp Mono组成组件:C#编译器,CIL虚拟机,核心类别的程序库,Mono的编译器负责生成符合公共语言规范的映射代码Mono打包的主要流程:c#script,unityspcript和第三方库......
  • Unity渲染管线的流程
    来自https://www.jianshu.com/p/02eebbf9ad9a 渲染管线的流程是在GPU中进行的,它主要占有计算机的显存部分。渲染管线在这个过程中进行了顶点处理、面处理、光栅化、像素处理。 1.顶点处理3D模型是由众多点构成的面展现出来的。顶点处理:是通过一系列坐标系的变换,让每个......
  • Unity中的OnApplicationPause和OnApplicationFocus
    OnApplicationPause游戏进入后台和返回前台//游戏进入后台时执行该方法pause为true切换回前台时pause为falsevoidOnApplicationPause(boolpause){if(pause){//切换到后台时执行}else{//切换到前台时执行,游戏启动时执行一次}}......
  • Unity遮挡剔除使用
    (1)设置对象为遮挡物or被遮挡物选中某个对象后,在其Occlusion窗口可以设置其Occluder Static和OccludeeStatic属性:遮挡物通过勾选一个对象的OccluderStatic可将其设置为静态遮挡物。理想的遮挡物应该是实心的,体积较大的物体。原则1:可能阻挡到其它对象的对象才应该被设置......
  • .NET 中 Channel 类(内存级消息队列)简单使用
    Channel是干什么的#TheSystem.Threading.Channelsnamespaceprovidesasetofsynchronizationdatastructuresforpassingdatabetweenproducersandconsumersasynchronously.Thelibrarytargets.NETStandardandworksonall.NETimplementations.Channelsa......
  • Linux如何给根目录扩容内存
    第一种:LVM分区格式,就是用系统默认的自动分区格式1.添加一块20G大小的nvme硬盘2.启动后,查看硬盘是否已经被系统识别3.对/dev/nvme0n2进行分区,并设置分区属性fdisk/dev/nvme0n2#然后输入npenterenterentertL8ew#t:修改分区文件系统id,选择8e,与原有分区属性一致(l......
  • Redis内存回收与缓存问题
    内存回收:1.过期key处理通过expire命令给key设置ttlRedis本身是KV型数据库,所有数据都存在RedisDB结构体中,其中有两张哈希表dict:用于存放KV(这里K是K,V是V)expires:保存Redis中所有的设置了过期时间的KEY以及到期时间TTL(这里K是K,V是TTL)过期KEY有两种删除策略:惰性删除,有......