unity 安卓unity5.6 profilerr看不到gc回收吗

程序写累了,就来玩玩酷跑小游戏吧,嘿嘿。
雨松MOMO送你一首歌曲,嘿嘿。
Unity3D研究院之UGUI安卓自动ETC1通道分离(九十一)
Unity3D研究院之UGUI安卓自动ETC1通道分离(九十一)
围观17511次
编辑日期: 字体:
网上已经有类似的解决方案,原理就是在做一张通道图,然后在shader里混合。代价就是需要做个shader在做一个脚本代替Image 和RawImage。
今天我发现其实unity自己已经做了这个功能了,不用写shader,也不用替换脚本,原生支持就是方便啊。
先看看我的图集,ETC1 压缩格式
1.升级你的unity到5.5(有可能unity5.4也可以)
2.Sprite必须设置Packing Tag(只有图集才支持)
3.设置所有UI贴图的格式为ETC1(无论透明还是不透明)
4.如下图所示,勾选Compress using ETC1(split alpha channel) (可以写个工具自动批量修改)
测试机器是小米pad1,先看看真机上的效果。
今天我还发现了另外一个问题,在某些android机器上可能会显示成这样。
我第一反应这肯定是unity的bug,我想办法解决了一下。
下载unity5.5的内置shader。有一个叫UI-DefaultETC1的shader拖入工程,在ProjectSetting-&GraphicsSettings里添加进去。
真机Profiler下,很清楚的看见透明图已经被unity分离了。
大家请在看上面的安卓真机Profiler的图。Unity把一张的图拆分成 0.5M大小 和1M大小 的两张图, 0.5M 就是ETC1的压缩图, 另外一个1M 我猜就是对应 Alpha
8 格式。 这样就算图片有的透明渐变的效果也会是很好的。 强烈建议大家使用啊~ 如果是1024大小 这样从truecolor的4M减少到现在1.5M 效果还是很可观的。
批量自动设置etc1 + alpha脚本,注意第二个参数传入true就可以
textureImporter.SetPlatformTextureSettings(“Android&#,TextureImporterFormat.ETC_RGB4,true);
大家赶紧测试吧。。 有问题欢迎在下面给我留言哈。。
本文固定链接:
转载请注明:
雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
如果您愿意花10块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。
您可能还会对这些文章感兴趣![Unity3D]关于Android真机调测Profiler
U3D中的Profile也是可以直接在链接安卓设备运行游戏下查看的,导出真机链接U3D的Profile看数据,这样能更好的测试具体原因。
大概看了下官方的做法,看了几张帖子顺带把做法记录下来。
参考:/Documentation/Manual/Profiler.html
用安卓真机调测Profile的数据,其实就两种方法,WIFI和ADB的方式。其实一般用的都是ADB方式,因为很少情况是你的电脑与手机是在同一子网WIFI下的。除非是你个人的网络。
如果你需要导出的游戏在非导出的机子上PROFILE的话,最好在导出的机子随便先导个带PROFILE的空项目。这样才能很容易链接上,不然是很麻烦的事情。
1.以WIFI的方式。
这个方法需要电脑所在的网络和手机所在的网络在同一子网下才能进行。
(1)导出U3D项目的时候勾选Profile,安装游戏到手机。
(2)打开游戏的时候,同时打开window-&profiler,然后选择选择输入IP,连上即可。
2.以ADB的方式。
注意防火墙是否屏蔽掉了端口,若屏蔽就打开。
(1)导出U3D项目的时候勾选Profile,安装游戏到手机。
(2)打开CMD命令行,cd到adb根目录,然后输入以下命令。将下面"这里加你的包名"改为你游戏打包出来的名,即Bundle
Identifier。例如:pany.aaa。
forward tcp:54999 localabstract:Unity-这里加你的包名
(3)打开游戏的时候,同时打开window-&profiler,然后选择选择AndroidProfiler(ADB@127.0.0.1:54999),连上即可。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。Unity Profiler工具的使用
我的图书馆
Unity Profiler工具的使用
转载自:http://blog.csdn/yangyy753/article/details/
为我们提供了一个强大的性能分析工具Profiler。今天我们就使用Profiler来详细分析一下官方例子AngryBots的内存使用信息数据。
首先打开Profiler选择Memory选项,在游戏运行的某一帧查看Detailed选项数据(Simple模式的数据很直观,可以知道内存大体被哪部分占用了,网上也有很多相关介绍,我就不再啰嗦了),如下图所示:
选中后,unity会自动获取这一帧的内存占用数据项,主要分为:Other、Assets、BuiltinResources、Scene Memory、NotSaved这五大部分,下面我们就来一一分析。
记录数据项很多,篇幅时间有限,我们就专挑占用大小排行榜靠前的几项来详细分析吧。
System.ExecutableAndDlls:系统可执行程序和DLL,是只读的内存,用来执行所有的脚本和DLL引用。不同平台和不同硬件得到的值会不一样,可以通过修改Player Setting的Stripping
Level来调节大小。
Ricky:我试着修改了一下Stripping Level似乎没什么改变,感觉虽占用内存大但不会影响游戏运行。我们暂时忽略它吧(- -)!
GfxClientDevice:GFX(图形加速\图形加速器\显卡 (GraphicsForce Express))客户端设备。
Ricky:虽占用较大内存,但这也是必备项,没办法优化。继续忽略吧(- -)!!
ManagedHeap.UsedSize:托管堆使用大小。
Ricky:重点监控对象,不要让它超过20MB,否则可能会有性能问题!
ShaderLab:Unity自带的着色器语言工具相关资源。
Ricky:这个东西大家都比较熟悉了,忽略它吧。
SerializedFile:序列化文件,把显示中的Prefab、Atlas和metadata等资源加载进内存。
Ricky:重点监控对象,这里就是你要监控的哪些预设在序列化中在内存中占用大小,根据需求进行优化。
PersistentManager.Remapper:持久化数据重映射管理相关
Ricky:与持久化数据相关,比如AssetBundle之类的。注意监控相关的文件。
ManagedHeap.ReservedUnusedSize:托管堆预留不使用内存大小,只由Mono使用。
Ricky:无法优化。
Texture2D:&2D贴图及纹理。
Ricky:重点优化对象,有以下几点可以优化:
许多贴图采用的Format格式是ARGB 32 bit所以保真度很高但占用的内存也很大。在不失真的前提下,适当压缩贴图,使用ARGB 16 bit就会减少一倍,如果继续Android采用RGBA Compressed ETC2 8 bits(iOS采用RGBA Compressed PVRTC 4 bits),又可以再减少一倍。把不需要透贴但有alpha通道的贴图,全都转换格式Android:RGB
Compressed ETC 4 bits,iOS:RGB Compressed PVRTC 4 bits。当加载一个新的Prefab或贴图,不及时回收,它就会永驻在内存中,就算切换场景也不会销毁。应该确定物体不再使用或长时间不使用就先把物体制空(null),然后调用Resources.UnloadUnusedAssets(),才能真正释放内存。有大量空白的图集贴图,可以用TexturePacker等工具进行优化或考虑合并到其他图集中。
AudioManager:音频管理器
Ricky:随着音频文件的增多而增大。
AudioClip:音效及声音文件
Ricky:重点优化对象,播放时长较长的音乐文件需要进行压缩成.mp3或.ogg格式,时长较短的音效文件可以使用.wav 或.aiff格式。
Cubemap:立方图纹理
Ricky:这个一般在天空盒中比较常见,我也不知道如何优化这个。。。
Mesh:模型网格
Ricky:主要检查是否有重复的资源,还有尽量减少点面数。
Scene Memory
Mesh:场景中使用的网格模型
Ricky:注意网格模型的点面数,能合并的mesh尽量合并。
Builtin Resources
Ricky:这些都是Unity的一些内部资源,对于项目内存没有什么分析价值,所以我就暂时不对其进行分析了。
Profiler内存重点关注优化项目
1)ManagedHeap.UsedSize: 移动游戏建议不要超过20MB.
2)SerializedFile: 通过异步加载(LoadFromCache、WWW等)的时候留下的序列化文件,可监视是否被卸载.
3)WebStream: 通过异步WWW下载的资源文件在内存中的解压版本,比SerializedFile大几倍或几十倍,不过我们现在项目中展示没有。
4)Texture2D: 重点检查是否有重复资源和超大Memory是否需要压缩等.
5)AnimationClip: 重点检查是否有重复资源.
6)Mesh: 重点检查是否有重复资源.
项目中可能遇到的问题
1.Device.Present:
1)GPU的presentdevice确实非常耗时,一般出现在使用了非常复杂的shader.
2)GPU运行的非常快,而由于Vsync的原因,使得它需要等待较长的时间.
3)同样是Vsync的原因,但其他线程非常耗时,所以导致该等待时间很长,比如:过量AssetBundle加载时容易出现该问题.
4)Shader.CreateGPUProgram:Shader在runtime阶段(非预加载)会出现卡顿(华为K3V2芯片).
5)StackTraceUtility.PostprocessStacktrace()和StackTraceUtility.ExtractStackTrace(): 一般是由Debug.Log或类似API造成,游戏发布后需将Debug API进行屏蔽。
2.Overhead:
1)一般情况为Vsync所致.
2)通常出现在设备上.
3.GC.Collect:
1)代码分配内存过量(恶性的)
2)一定时间间隔由系统调用(良性的).
占用时间:
1)与现有Garbage size相关
2)与剩余内存使用颗粒相关(比如场景物件过多,利用率低的情况下,GC释放后需要做内存重排)
4.GarbageCollectAssetsProfile:
1)引擎在执行UnloadUnusedAssets操作(该操作是比较耗时的,建议在切场景的时候进行)。
2)尽可能地避免使用Unity内建GUI,避免GUI.Repaint过渡GCAllow.
3)if(other.tag == a.tag)改为pareTag(a.tag).因为other.tag为产生180B的GC Allow.
4)少用foreach,因为每次foreach为产生一个enumerator(约16B的内存分配),尽量改为for.
5)Lambda表达式,使用不当会产生内存泄漏.
5.尽量少用LINQ:
1)部分功能无法在某些平台使用.
2)会分配大量GC Allow.
6.控制StartCoroutine的次数:
1)开启一个Coroutine(协程),至少分配37B的内存.
2)Coroutine类的实例 -& 21B.
3)Enumerator -& 16B.
7.使用StringBuilder替代字符串直接连接.
8.缓存组件:
1)每次GetComponent均会分配一定的GC Allow.
2)每次Object.name都会分配39B的堆内存.
Ricky Yang个人原创,版权所有,转载注明,谢谢。http://blog.csdn/yangyy753
TA的最新馆藏
喜欢该文的人也喜欢A. WaitForTargetFPS:&
& & & Vsync(垂直同步)功能所,即显示当前帧的CPU等待时间&
& &B. Overhead:&
& & & Profiler总体时间-所有单项的记录时间总和。用于记录尚不明确的时间消耗,以帮助进一步完善Profiler的统计。&
& & & & C. Physics.Simulate:&
& & & 当前帧物理模拟的CPU占用时间。&
& &D. Camera.Render:&
& & & 相机渲染准备工作的CPU占用量&
& &E. RenderTexture.SetActive:&
& & & 设置RenderTexture操作.&
& & & 底层实现:1.比对当前帧与前一帧的ColorSurface和DepthSurface.&
& & & & & & & &2.如果这两个Buffer一致则不生成新的RT,否则则生成新的RT,并设置与之相对应的Viewport和空间转换矩阵.&
& &F. Monobehaviour.OnMouse_ :&
& & & 用于检测鼠标的输入消息接收和反馈,主要包括:SendMouseEvents和DoSendMouseEvents。(只要Edtor开起来,这个就会存在)&
& &G. HandleUtility.SetViewInfo:&
& & & 仅用于Editor中,作用是将GUI和Editor中的显示看起来与发布版本的显示一致。&
H. GUI.Repaint:&
& & & GUI的重绘(说明在有使用原生的OnGUI)&
& &I. Event.Internal_MakeMasterEventCurrent:&
& & & 负责GUI的消息传送&
& &J. Cleanup Unused Cached Data:&
& & & 清空无用的缓存数据,主要包括RenderBuffer的垃圾回收和TextRendering的垃圾回收。&
& & & & &1.RenderTexture.GarbageCollectTemporary:存在于RenderBuffer的垃圾回收中,清除临时的FreeTexture.&
& & & & &2.TextRendering.Cleanup:TextMesh的垃圾回收操作&
& &K. Application.Integrate Assets in Background:&
& & & 遍历预加载的线程队列并完成加载,同时,完成纹理的加载、Substance的Update等.&
& &L. Application.LoadLevelAsync Integrate:&
& & & 加载场景的CPU占用,通常如果此项时间长的话70%的可能是Texture过长导致.&
& &M. UnloadScene:&
& & & 卸载场景中的GameObjects、Component和GameManager,一般用在切换场景时.&
& &N. CollectGameObjectObjects:&
& & & 执行上面M项的同时,会将场景中的GameObject和Component聚集到一个Array中.然后执行下面的Destroy.&
& &O. Destroy:&
& & & 删除GameObject和Component的CPU占用.&
& &P. AssetBundle.LoadAsync Integrate:&
& & & 多线程加载AwakeQueue中的内容,即多线程执行资源的AwakeFromLoad函数.&
& &Q. Loading.AwakeFromLoad:&
& & & 在资源被加载后调用,对每种资源进行与其对应用处理.&
2. CPU Usage&
& &A. Device.Present:&
& & & device.PresentFrame的耗时显示,该选项出现在发布版本中.&
& &B. Graphics.PresentAndSync:&
& & & GPU上的显示和垂直同步耗时.该选项出现在发布版本中.&
& &C. Mesh.DrawVBO:&
& & & GPU中关于Mesh的Vertex Buffer Object的渲染耗时.&
& &D. Shader.Parse:&
& & & 资源加入后引擎对Shader的解析过程.&
& &E. Shader.CreateGPUProgram:&
& & & 根据当前设备支持的图形库来建立GPU工程.&
3. Memory Profiler&
& &A. Used Total:&
& & & 当前帧的Unity内存、Mono内存、GfxDriver内存、Profiler内存的总和.&
& &B. Reserved Total:&
& & & 系统在当前帧的申请内存.&
& &C. Total System Memory Usage:&
& & & 当前帧的虚拟内存使用量.(通常是我们当前使用内存的1.5~3倍)&
& &D. GameObjects in Scene:&
& & & 当前帧场景中的GameObject数量.&
& &E. Total Objects in Scene:&
& & & 当前帧场景中的Object数量(除GameObject外,还有Component等).&
& &F. Total Object Count:&
& & & Object数据 + Asset数量.&
4. Detail Memory Profiler&
& &A. Assets:&
& & & Texture2d:记录当前帧内存中所使用的纹理资源情况,包括各种GameObject的纹理、天空盒纹理以及场景中所用的Lightmap资源.&
& &B. Scene Memory:&
& & & 记录当前场景中各个方面的内存占用情况,包括GameObject、所用资源、各种组件以及GameManager等(天般情况通过AssetBundle加载的不会显示在这里).&
& &A. Other:&
& & & ManagedHeap.UseSize:代码在运行时造成的堆内存分配,表示上次GC到目前为止所分配的堆内存量.&
& & & SerializedFile(3):&
& & & WebStream:这个是由WWW来进行加载的内存占用.&
& & & System.ExecutableAndDlls:不同平台和不同硬件得到的值会不一样。&
5. 优化重点&
& &A. CPU-GC Allow:&
& & & 关注原则:1.检测任何一次性内存分配大于2KB的选项 2.检测每帧都具有20B以上内存分配的选项.&
& &B. Time ms:&
& & & 记录游戏运行时每帧CPU占用(特别注意占用5ms以上的).&
& &C. Memory Profiler-Other:&
& & & 1.ManagedHeap.UsedSize: 移动游戏建议不要超过20MB.&
& & & 2.SerializedFile: 通过异步加载(LoadFromCache、WWW等)的时候留下的序列化文件,可监视是否被卸载.&
& & & 3.WebStream: 通过异步WWW下载的资源文件在内存中的解压版本,比SerializedFile大几倍或几十倍,重点监视.****&
& &D. Memory Profiler-Assets:&
& & & 1.Texture2D: 重点检查是否有重复资源和超大Memory是否需要压缩等.&
& & & 2.AnimationClip: 重点检查是否有重复资源.&
& & & 3.Mesh: 重点检查是否有重复资源.&
6. 项目中可能遇到的问题&
& &A. Device.Present:&
& & & 1.GPU的presentdevice确实非常耗时,一般出现在使用了非常复杂的shader.&
& & & 2.GPU运行的非常快,而由于Vsync的原因,使得它需要等待较长的时间.&
& & & 3.同样是Vsync的原因,但其他线程非常耗时,所以导致该等待时间很长,比如:过量AssetBundle加载时容易出现该问题.&
& & & 4.Shader.CreateGPUProgram:Shader在runtime阶段(非预加载)会出现卡顿(华为K3V2芯片).&
& &B. StackTraceUtility.PostprocessStacktrace()和StackTraceUtility.ExtractStackTrace():&
& & & 1.一般是由Debug.Log或类似API造成.&
& & & 2.游戏发布后需将Debug API进行屏蔽.&
& &C. Overhead:&
& & & 1.一般情况为Vsync所致.&
& & & 2.通常出现在Android设备上.&
& &D. GC.Collect:&
& & & 原因: 1.代码分配内存过量(恶性的) 2.一定时间间隔由系统调用(良性的).&
& & & 占用时间:1.与现有Garbage size相关 2.与剩余内存使用颗粒相关(比如场景物件过多,利用率低的情况下,GC释放后需要做内存重排)&
& &E. GarbageCollectAssetsProfile:&
& & & 1.引擎在执行UnloadUnusedAssets操作(该操作是比较耗时的,建议在切场景的时候进行).&
& & & 2.尽可能地避免使用Unity内建GUI,避免GUI.Repaint过渡GC Allow.&
& & & 3.if(other.tag == GearParent.MogoPlayerTag)改为pareTag(GearParent.MogoPlayerTag).因为other.tag为产生180B的GC
& &F. 少用foreach,因为每次foreach为产生一个enumerator(约16B的内存分配),尽量改为for.&
& &G. Lambda表达式,使用不当会产生内存泄漏.&
& &H. 尽量少用LINQ:&
& & & 1.部分功能无法在某些平台使用.&
& & & 2.会分配大量GC Allow.&
& &I. 控制StartCoroutine的次数:&
& & & 1.开启一个Coroutine(协程),至少分配37B的内存.&
& & & 2.Coroutine类的实例 -- 21B.&
& & & 3.Enumerator -- 16B.&
& &J. 使用StringBuilder替代字符串直接连接.&
& &K. 缓存组件:&
& & & 1.每次GetComponent均会分配一定的GC Allow.&
& & & 2.每次Object.name都会分配39B的堆内存.
本文已收录于以下专栏:
相关文章推荐
转载自:http://blog.csdn.net/yangyy753/article/details/
Unity3D为我们提供了一个强大的性能分析工具Profiler。今...
1、Unity内置分析器Profiler、Xcode分析工具
2、“优化”的定位:手机比PC有更多性能瓶颈,对游戏软件有严格要求。项目开发过程中,性能优化是最重要、最艰难、最容易被忽略、最可能导致失败...
网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。
这几天都在安卓手机上测试游戏,通过Profiler 查看Memory 发现,SerializedFile 占用了大部分内存,游戏内存占用一度达到400M,导致频繁崩溃。初步理解为,Serialized...
以图行可视化方式直观显示资源所占内存百分比[包括模型、贴图、动画序列、字体等]
以索引列表说明当前游戏对象之间的引用关系,[包括类名、ID、销毁状态等]
方便非专业技术人员比如TA、美术设计师查看当前...
个人认为Unity相比于其他引擎易用性较好的原因主要有:
基于组件(Component)的关卡设计更符合直觉
Unity通过将系统或用户的脚本抽象为可重用的组件(Component)并附着在游...
通过USB ADB
1.从Unity中Export Android 工程的时候一定要勾选 Development Build,autoconnect profiler
2.cmd进入adb的目录(打开...
A. WaitForTargetFPS: 
      Vsync(垂直同步)功能所,即显示当前帧的CPU等待时间 
   B. Overhead: 
      Profiler总体...
A. WaitForTargetFPS: 
      Vsync(垂直同步)功能所,即显示当前帧的CPU等待时间 
   B. Overhead: 
      Profiler总体...
A. WaitForTargetFPS: 
      Vsync(垂直同步)功能所,即显示当前帧的CPU等待时间
B. Overhead: 
      Profiler总体时间-所有...
1. CPU Usage
A. WaitForTargetFPS: Vsync(垂直同步)功能所,即显示当前帧的CPU等待时间
B. Overhead: Profiler总体时间-所...
他的最新文章
讲师:董西成
讲师:唐宇迪
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)  之前一直对 Unity 中的 GC 是没有什么概念的,对内存管理的概念也都比较模糊了,直到上周公司的技术总监在做技术分享会的时候讲了一下 GC,这才对 Unity 中的 GC 有了一定的了解,知识不敢独享,因此拿出来和大家一起学习一下,共同进步!俗话说得好,一图胜千言,常见的 Unity GC 知识点总结出来就是下面这样思维导图,一目了然。
  可以到这里下载。
Unity GC分析及优化
  后来知道了,原来总监也是参考的 &总结并绘制出来的上面的脑图。正巧在博客园发现了已经有位大神()把原文翻译出来了,而且质量很高~,&在这里。下面我就可耻地把译文搬运了过来,作为上面思维导图的知识点补充。(要我自己讲或者翻译肯定没有人家总结翻译的到位,索性就把大神的译文搬运了过来。)
在游戏运行的时候,数据主要存储在内存中,当游戏的数据不在需要的时候,存储当前数据的内存就可以被回收再次使用。内存垃圾是指当前废弃数据所占用的内存,垃圾回收(GC)是指将废弃的内存重新回收再次使用的过程。
Unity中将垃圾回收当作内存管理的一部分,如果游戏中垃圾回收十分复杂,则游戏的性能会受到极大影响,此时垃圾回收会成为游戏性能的一大障碍点。
下面我们将会学习垃圾回收的机制,掌握垃圾回收如何被触发以及如何提高垃圾回收效率来减小其对游戏行性能的影响。
Unity内存管理机制简介
要想了解垃圾回收如何工作以及何时被触发,我们首先需要了解unity的内存管理机制。Unity主要采用自动内存管理的机制,开发时在代码中不需要详细地告诉unity如何进行内存管理,unity内部自身会进行内存管理。
unity的自动内存管理可以理解为以下几个部分:
unity内部有两个内存管理池:堆内存和堆栈内存。堆栈内存(stack)主要用来存储较小的和短暂的数据片段,堆内存(heap)主要用来存储较大的和存储时间较长的数据片段。
unity中的变量只会在堆栈或者堆内存上进行内存分配。
只要变量处于激活状态,则其占用的内存会被标记为使用状态,则该部分的内存处于被分配的状态,变量要么存储在堆栈内存上,要么处于堆内存上。
一旦变量不再激活,则其所占用的内存不再需要,该部分内存可以被回收到内存池中被再次使用,这样的操作就是内存回收。处于堆栈上的内存回收及其快速,处于堆上的内存并不是及时回收的,其对应的内存依然会被标记为使用状态。
垃圾回收主要是指堆上的内存分配和回收,unity中会定时对堆内存进行GC操作。
在了解了GC的过程后,下面详细了解堆内存和堆栈内存的分配和回收机制的差别。
栈内存分配和回收机制
栈上的内存分配和回收十分快捷简单,主要是栈上只会存储短暂的较小的变量。内存分配和回收都会以一种可控制顺序和大小的方式进行。
栈的运行方式就像&&:只是一个数据的集合,数据的进出都以一种固定的方式运行。正是这种简洁性和固定性使得堆栈的操作十分快捷。当数据被存储在栈上的时候,只需要简单地在其后进行扩展。当数据失效的时候,只需要将其从栈上移除复用。
堆内存分配和回收机制
堆内存上的内存分配和存储相对而言更加复杂,主要是堆内存上可以存储短期较小的数据,也可以存储各种类型和大小的数据。其上的内存分配和回收顺序并不可控,可能会要求分配不同大小的内存单元来存储数据。
堆上的变量在存储的时候,主要分为以下几步:
首先,unity检测是否有足够的闲置内存单元用来存储数据,如果有,则分配对应的内存单元;
如果没有足够的存储单元,unity会触发垃圾回收来释放不再被使用的堆内存。这步操作是一步缓慢的操作,如果垃圾回收后有足够的内存单元,则进行内存分配。
如果垃圾回收后并没有足够的内存单元,则unity会扩展堆内存的大小,这步操作会很缓慢,然后分配对应的内存单元给变量。
堆内存的分配有可能会变得十分缓慢,特别是需要垃圾回收和堆内存需要扩展的情况下。
垃圾回收时的操作
当一个变量不再处于激活状态的时候,其所占用的内存并不会立刻被回收,不再使用的内存只会在GC的时候才会被回收。
每次运行GC的时候,主要进行下面的操作:
GC会检查堆内存上的每个存储变量;
对每个变量会检测其引用是否处于激活状态;
如果变量的引用不再处于激活状态,则会被标记为可回收;
被标记的变量会被移除,其所占有的内存会被回收到堆内存上。
GC操作是一个极其耗费的操作,堆内存上的变量或者引用越多则其运行的操作会更多,耗费的时间越长。
何时会触发垃圾回收
主要有三个操作会触发垃圾回收:
在堆内存上进行内存分配操作而内存不够的时候都会触发垃圾回收来利用闲置的内存;
GC会自动的触发,不同平台运行频率不一样;
GC可以被强制执行。
GC操作可以被频繁触发,特别是在堆内存上进行内存分配时内存单元不足够的时候,这就意味着频繁在堆内存上进行内存分配和回收会触发频繁的GC操作。
GC操作带来的问题
在了解GC在unity内存管理中的作用后,我们需要考虑其带来的问题。最明显的问题是GC操作会需要大量的时间来运行,如果堆内存上有大量的变量或者引用需要检查,则检查的操作会十分缓慢,这就会使得游戏运行缓慢。其次GC可能会在关键时候运行,例如CPU处于游戏的性能运行关键时刻,其他的任何一个额外的操作都可能会带来极大的影响,使得游戏帧率下降。
另外一个GC带来的问题是堆内存碎片。当一个内存单元从堆内存上分配出来,其大小取决于其存储的变量的大小。当该内存被回收到堆内存上的时候,有可能使得堆内存被分割成碎片化的单元。也就是说堆内存总体可以使用的内存单元较大,但是单独的内存单元较小,在下次内存分配的时候不能找到合适大小的存储单元,这就会触发GC操作或者堆内存扩展操作。
堆内存碎片会造成两个结果,一个是游戏占用的内存会越来越大,一个是GC会更加频繁地被触发。
分析GC带来的问题
GC操作带来的问题主要表现为帧率运行低,性能间歇中断或者降低。如果游戏有这样的表现,则首先需要打开unity中的profiler window来确定是否是GC造成。
了解如何运用profiler window,可以参考,如果游戏确实是由GC造成的,可以继续阅读下面的内容。
分析堆内存的分配
如果GC造成游戏的性能问题,我们需要知道游戏中的哪部分代码会造成GC,内存垃圾在变量不再激活的时候产生,所以首先我们需要知道堆内存上分配的是什么变量。
  堆内存和堆栈内存分配的变量类型
&  在Unity中,值类型变量都在堆栈上进行内存分配,其他类型的变量都在堆内存上分配。如果你不知道值类型和引用类型的差别,可以查看。
  &下面的代码可以用来理解值类型的分配和释放,其对应的变量在函数调用完后会立即回收:
void ExampleFunciton()
int localInt = 5;
  对应的引用类型的参考代码如下,其对应的变量在GC的时候才回收:
void ExampleFunction()
List localList = new List();
  利用profiler window 来检测堆内存分配:
&  我们可以在profier window中检查堆内存的分配操作:在CPU usage分析窗口中,我们可以检测任何一帧cpu的内存分配情况。其中一个选项是GC alloc,通过分析其来定位是什么函数造成大量的堆内存分配操作。一旦定位该函数,我们就可以分析解决其造成问题的原因从而减少内存垃圾的产生。
降低GC的影响的方法
大体上来说,我们可以通过三种方法来降低GC的影响:
减少GC的运行次数;
减少单次GC的运行时间;
将GC的运行时间延迟,避免在关键时候触发,比如可以在场景加载的时候调用GC
基于此,我们可以采用三种策略:
对游戏进行重构,减少堆内存的分配和引用的分配。更少的变量和引用会减少GC操作中的检测个数从而提高GC的运行效率。
降低堆内存分配和回收的频率,尤其是在关键时刻。也就是说更少的事件触发GC操作,同时也降低堆内存碎片。
我们可以试着测量GC和堆内存扩展的时间,使其按照可预测的顺序执行。当然这样操作的难度极大,但是这会大大降低GC的影响。
减少内存垃圾的数量
减少内存垃圾主要可以通过一些方法来减少:
&  如果在代码中反复调用某些造成堆内存分配的函数但是其返回结果并没有使用,这就会造成不必要的内存垃圾,我们可以缓存这些变量来重复利用,这就是缓存。
   例如下面的代码每次调用的时候就会造成堆内存分配,主要是每次都会分配一个新的数组:
void OnTriggerEnter(Collider other)
Renderer[] allRenderers = FindObjectsOfType&Renderer&();
ExampleFunction(allRenderers);
  对比下面的代码,只会生产一个数组用来缓存数据,实现反复利用而不需要造成更多的内存垃圾:
private Renderer[] allR
void Start()
allRenderers = FindObjectsOfType&Renderer&();
void OnTriggerEnter(Collider other)
ExampleFunction(allRenderers);
  不要在频繁调用的函数中反复进行堆内存分配
  在MonoBehaviour中,如果我们需要进行堆内存分配,最坏的情况就是在其反复调用的函数中进行堆内存分配,例如Update()和LateUpdate()函数这种每帧都调用的函数,这会造成大量的内存垃圾。我们可以考虑在Start()或者Awake()函数中进行内存分配,这样可以减少内存垃圾。
  下面的例子中,update函数会多次触发内存垃圾的产生:
void Update()
ExampleGarbageGenerationFunction(transform.position.x);
  通过一个简单的改变,我们可以确保每次在x改变的时候才触发函数调用,这样避免每帧都进行堆内存分配:
private float previousTransformPositionX;
void Update()
float transformPositionX = transform.position.x;
if(transfromPositionX != previousTransformPositionX)
ExampleGarbageGenerationFunction(transformPositionX);
previousTransformPositionX = trasnformPositionX;
  另外的一种方法是在update中采用计时器,特别是在运行有规律但是不需要每帧都运行的代码中,例如:
void Update()
ExampleGarbageGeneratiingFunction()
  通过添加一个计时器,我们可以确保每隔1s才触发该函数一次:
private float timeSinceLastC
private float delay = 1f;
void Update()
timSinceLastCalled += Time.deltaT
if(timeSinceLastCalled & delay)
ExampleGarbageGenerationFunction();
timeSinceLastCalled = 0f;
  通过这样细小的改变,我们可以使得代码运行的更快同时减少内存垃圾的产生。
  在堆内存上进行链表的分配的时候,如果该链表需要多次反复的分配,我们可以采用链表的clear函数来清空链表从而替代反复多次的创建分配链表。
void Update()
List myList = new List();
PopulateList(myList);
  通过改进,我们可以将该链表只在第一次创建或者该链表必须重新设置的时候才进行堆内存分配,从而大大减少内存垃圾的产生:
private List myList = new List();
void Update()
myList.Clear();
PopulateList(myList);
  即便我们在代码中尽可能地减少堆内存的分配行为,但是如果游戏有大量的对象需要产生和销毁依然会造成GC。对象池技术可以通过重复使用objects来降低堆内存的分配和回收频率。对象池在游戏中广泛的使用,特别是在游戏中需要频繁的创建和销毁相同的游戏对象的时候,例如枪的子弹。
  要详细的讲解对象池已经超出本文的范围,但是该技术值得我们深入的研究对于对象池有详细深入的讲解。
造成不必要的堆内存分配的因素
我们已经知道值类型变量在堆栈上分配,其他的变量在堆内存上分配,但是任然有一些情况下的堆内存分配会让我们感到吃惊。下面让我们分析一些常见的不必要的堆内存分配行为并对其进行优化。
  字符串  
&  在c#中,字符串是引用类型变量而不是值类型变量,即使看起来它是存储字符串的值的。这就意味着字符串会造成一定的内存垃圾,由于代码中经常使用字符串,所以我们需要对其格外小心。
  c#中的字符串是不可变更的,也就是说其内部的值在创建后是不可被变更的。每次在对字符串进行操作的时候(例如运用字符串的&加&操作),unity会新建一个字符串用来存储新的字符串,使得旧的字符串被废弃,这样就会造成内存垃圾。
  我们可以采用以下的一些方法来最小化字符串的影响:
  1)减少不必要的字符串的创建,如果一个字符串被多次利用,我们可以创建并缓存该字符串。
  2)减少不必要的字符串操作,例如如果在Text组件中,有一部分字符串需要经常改变,但是其他部分不会,则我们可以将其分为两个部分的组件。
  3)如果我们需要实时的创建字符串,我们可以采用StringBuilderClass来代替,StringBuilder专为不需要进行内存分配而设计,从而减少字符串产生的内存垃圾。
  4)移除游戏中的Debug.Log()函数的代码,尽管该函数可能输出为空,对该函数的调用依然会执行,该函数会创建至少一个字符(空字符)的字符串。如果游戏中有大量的该函数的调用,这会造成内存垃圾的增加。
  在下面的代码中,在Update函数中会进行一个string的操作,这样的操作就会造成不必要的内存垃圾:
public Text timerT
private float
void Update()
timer += Time.deltaT
timerText.text = "Time:"+ timer.ToString();
  通过将字符串进行分隔,我们可以剔除字符串的加操作,从而减少不必要的内存垃圾:
public Text timerHeaderT
public Text timerValueT
private float
void Start()
timerHeaderText.text = "TIME:";
void Update()
timerValueText.text = timer.ToString();
Unity函数调用
  在代码编程中,我们需要知道当我们调用不是我们自己编写的代码,无论是Unity自带的还是插件中的,我们都可能会产生内存垃圾。Unity的某些函数调用会产生内存垃圾,我们在使用的时候需要注意它的使用。
  这儿没有明确的列表指出哪些函数需要注意,每个函数在不同的情况下有不同的使用,所以最好仔细地分析游戏,定位内存垃圾的产生原因以及如何解决问题。有时候缓存是一种有效的办法,有时候尽量降低函数的调用频率是一种办法,有时候用其他函数来重构代码是一种办法。现在来分析unity中中常见的造成堆内存分配的函数调用。
  在Unity中如果函数需要返回一个数组,则一个新的数组会被分配出来用作结果返回,这不容易被注意到,特别是如果该函数含有迭代器,下面的代码中对于每个迭代器都会产生一个新的数组:
void ExampleFunction()
for(int i=0; i & myMesh.normals.Li++)
Vector3 normal = myMesh.normals[i];
  对于这样的问题,我们可以缓存一个数组的引用,这样只需要分配一个数组就可以实现相同的功能,从而减少内存垃圾的产生:
void ExampleFunction()
Vector3[] meshNormals = myMesh.
for(int i=0; i & meshNormals.Li++)
Vector3 normal = meshNormals[i];
此外另外的一个函数调用GameObject.name 或者 GameObject.tag也会造成预想不到的堆内存分配,这两个函数都会将结果存为新的字符串返回,这就会造成不必要的内存垃圾,对结果进行缓存是一种有效的办法,但是在Unity中都对应的有相关的函数来替代。对于比较gameObject的tag,可以采用pareTag()来替代。
  在下面的代码中,调用gameobject.tag就会产生内存垃圾:
private string playerTag="Player";
void OnTriggerEnter(Collider other)
bool isPlayer = other.gameObject.tag ==playerT
  采用pareTag()可以避免内存垃圾的产生:
private string playerTag = "Player";
void OnTriggerEnter(Collider other)
bool isPlayer = pareTag(playerTag);
  不只是pareTag,unity中许多其他的函数也可以避免内存垃圾的生成。比如我们可以用Input.GetTouch()和Input.touchCount()来代替Input.touches,或者用Physics.SphereCastNonAlloc()来代替Physics.SphereCastAll()。
  装箱操作是指一个值类型变量被用作引用类型变量时候的内部变换过程,如果我们向带有对象类型参数的函数传入值类型,这就会触发装箱操作。比如String.Format()函数需要传入字符串和对象类型参数,如果传入字符串和int类型数据,就会触发装箱操作。如下面代码所示:
void ExampleFunction()
int cost = 5;
string displayString = String.Format("Price:{0} gold",cost);
  在Unity的装箱操作中,对于值类型会在堆内存上分配一个System.Object类型的引用来封装该值类型变量,其对应的缓存就会产生内存垃圾。装箱操作是非常普遍的一种产生内存垃圾的行为,即使代码中没有直接的对变量进行装箱操作,在插件或者其他的函数中也有可能会产生。最好的解决办法是尽可能的避免或者移除造成装箱操作的代码。
  调用 StartCoroutine()会产生少量的内存垃圾,因为unity会生成实体来管理协程。所以在游戏的关键时刻应该限制该函数的调用。基于此,任何在游戏关键时刻调用的协程都需要特别的注意,特别是包含延迟回调的协程。
  yield在协程中不会产生堆内存分配,但是如果yield带有参数返回,则会造成不必要的内存垃圾,例如:
yield return 0;
  由于需要返回0,引发了装箱操作,所以会产生内存垃圾。这种情况下,为了避免内存垃圾,我们可以这样返回:
yield return null;
  另外一种对协程的错误使用是每次返回的时候都new同一个变量,例如:
while(!isComplete)
yield return new WaitForSeconds(1f);
  我们可以采用缓存来避免这样的内存垃圾产生:
WaitForSeconds delay = new WaiForSeconds(1f);
while(!isComplete)
yield return
  如果游戏中的协程产生了内存垃圾,我们可以考虑用其他的方式来替代协程。重构代码对于游戏而言十分复杂,但是对于协程而言我们也可以注意一些常见的操作,比如如果用协程来管理时间,最好在update函数中保持对时间的记录。如果用协程来控制游戏中事件的发生顺序,最好对于不同事件之间有一定的信息通信的方式。对于协程而言没有适合各种情况的方法,只有根据具体的代码来选择最好的解决办法。
foreach 循环
在unity5.5以前的版本中,在foreach的迭代中都会生成内存垃圾,主要来自于其后的装箱操作。每次在foreach迭代的时候,都会在堆内存上生产一个System.Object用来实现迭代循环操作。在unity5.5中解决了这个问题,比如,在unity5.5以前的版本中,用foreach实现循环:
void ExampleFunction(List listOfInts)
foreach(int currentInt in listOfInts)
DoSomething(currentInt);
  如果游戏工程不能升级到5.5以上,则可以用for或者while循环来解决这个问题,所以可以改为:
void ExampleFunction(List listOfInts)
for(int i=0; i & listOfInts.C i++)
int currentInt = listOfInts[i];
DoSomething(currentInt);
函数的引用,无论是指向匿名函数还是显式函数,在unity中都是引用类型变量,这都会在堆内存上进行分配。匿名函数的调用完成后都会增加内存的使用和堆内存的分配。具体函数的引用和终止都取决于操作平台和编译器设置,但是如果想减少GC最好减少函数的引用。
LINQ和常量表达式
由于LINQ和常量表达式以装箱的方式实现,所以在使用的时候最好进行性能测试。
重构代码来减小GC的影响
即使我们减小了代码在堆内存上的分配操作,代码也会增加GC的工作量。最常见的增加GC工作量的方式是让其检查它不必检查的对象。struct是值类型的变量,但是如果struct中包含有引用类型的变量,那么GC就必须检测整个struct。如果这样的操作很多,那么GC的工作量就大大增加。在下面的例子中struct包含一个string,那么整个struct都必须在GC中被检查:
public struct ItemData
public string
public int
public Vector3
private ItemData[] itemD
我们可以将该struct拆分为多个数组的形式,从而减小GC的工作量:
private string[] itemN
private int[] itemC
private Vector3[] itemP
另外一种在代码中增加GC工作量的方式是保存不必要的Object引用,在进行GC操作的时候会对堆内存上的object引用进行检查,越少的引用就意味着越少的检查工作量。在下面的例子中,当前的对话框中包含一个对下一个对话框引用,这就使得GC的时候回去检查下一个对象框:
public class DialogData
private DialogData nextD
public DialogData GetNextDialog()
return nextD
通过重构代码,我们可以返回下一个对话框实体的标记,而不是对话框实体本身,这样就没有多余的object引用,从而减少GC的工作量:
public class DialogData
private int nextDialogID;
public int GetNextDialogID()
return nextDialogID;
当然这个例子本身并不重要,但是如果我们的游戏中包含大量的含有对其他Object引用的object,我们可以考虑通过重构代码来减少GC的工作量。
定时执行GC操作
主动调用GC操作
&  如果我们知道堆内存在被分配后并没有被使用,我们希望可以主动地调用GC操作,或者在GC操作并不影响游戏体验的时候(例如场景切换的时候),我们可以主动的调用GC操作:
System.GC.Collect()
通过主动的调用,我们可以主动驱使GC操作来回收堆内存。
作者:马三小伙儿出处:&请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!
阅读(...) 评论()

我要回帖

更多关于 unity profiler adb 的文章

 

随机推荐