Unity-Profiler性能分析

Profiler窗口

1. CPU

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.GPU 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)改为other.CompareTag(GearParent.MogoPlayerTag).因为other.tag为产生180B的GC
Allow.

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的堆内存.

Unity优化系列文章:http://www.unity.5helpyou.com/tag/unity%E4%BC%98%E5%8C%96

本文转截自:http://www.unity.5helpyou.com/2791.html

UGUI降低填充率技巧两则

Fill
Rate(填充率)是指显卡每帧每秒能够渲染的像素数。在每帧绘制中,如果一个像素被反复绘制的次数越多,那么它占用的资源也必然更多。目前在移动设备上,FillRate
的压力主要来自半透明物体。因为多数情况下,半透明物体需要开启 Alpha Blend 且关闭 ZTest和 ZWrite,同时如果我们绘制像 alpha=0
这种实际上不会产生效果的颜色上去,也同样有 Blend 操作,这是一种极大的浪费。因此,今天我们为大家推荐两则UGUI 降低填充率的技巧,希望大家能受用。

这是侑虎科技第50篇原创文章,感谢作者钱康来供稿。欢迎转发分享,未经作者授权请勿转载。同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)

作者博客:http://qiankanglai.me

知乎专栏:<https://zhuanlan.zhihu.com/soulgame

在Unity中,与能直接看到的Verts/Tris/Batches数据不同,填充率并不能被直接统计到,但是我们可以通过查看OverDraw来大致查看:

对于UI来说,后者其实是很容易被忽视的热点(特别是对于中低端移动设备来说)。下面我就以具体两个例子为例,并探讨其解决思路。

滥用不可见组件

之前在Profile手头项目的时候发现红米上一个奇怪的现象:战斗界面维持60fps没问题;进入UI界面之后瞬间掉到45fps,甚至有的复杂界面掉到30fps。但战斗场景的Tris/Verts比UI高不少。

通过工具很方便的就定位到了瓶颈在于FillRate爆了,最后发现新手教学部分用了很多“不可见”的Image作为交互响应的控件;但这些东西虽然画上去没有效果,依然占用了显卡资源,特别是有很多大块的区域…找到问题之后就解决起来很方便:实现一个只在逻辑上响应Raycast但是不参与绘制的组件即可,改完之后帧率瞬间正常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using UnityEngine;



using System.Collections;



namespace UnityEngine.UI



{



    public class Empty4Raycast : MaskableGraphic



    {



        protected Empty4Raycast()



        {



            useLegacyMeshGeneration = false;



        }



        protected override void OnPopulateMesh(VertexHelper toFill)



        {



            toFill.Clear();



        }



    }



}

这里顺便提一句,显卡资源消耗在没有到瓶颈的时候,大概是随着使用的增加正相关,但是到瓶颈之后很多时候是“崩盘”节奏。

Polygon Mode Sprites

在UI部分中我们会大量使用图片作为元素,如果图片边缘有大片留白就会和上面那个问题一样,产生很多无用填充。Unity和Texture
Packer目前都支持了Polygon
Mode,也就是说将原来的矩形Sprite用更加紧致的Polygon来描述,从而能更有效的利用空白空间(顺便也减小了打包出来的图资源)。

当然,目前Unity只在Sprite Render里支持了这个模式,在UGUI的Image中还无法正常使用。我自己实现了一个挂官方论坛[UGUI Image
with polygon sprites](http://forum.unity3d.com/threads/ugui-image-with-
polygon-sprites.390039/),Texture Packer作者也表示很感兴趣~

可以看到同样的一个图片,新的模式下顶点数变多了,但是绘制的范围变小了不少;同时在打包的时候图片也更加的紧致了,因为在不规则大图周围能塞进去不少小的元素。

下面这个脚本是针对Image的扩展,使其支持Polygon Mode Sprite…不过精力有限,只支持了Simple而且没做Preserve
Aspect,有兴趣的朋友如果实现了别的模式还望多多交流(主要是Filled和Sliced下要自己重新划分三角形,想想就麻烦…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
using System.Collections.Generic;



namespace UnityEngine.UI



{



    [AddComponentMenu("UI/Effects/PolygonImage", 16)]



    [RequireComponent(typeof(Image))]



    public class PolygonImage : BaseMeshEffect



    {



        protected PolygonImage()



        { }



        // GC Friendly



        private static Vector3[] fourCorners = new Vector3[4];



        private static UIVertex vertice = new UIVertex();



        private RectTransform rectTransform = null;



        private Image image = null;



        public override void ModifyMesh(VertexHelper vh)



        {



            if (!isActiveAndEnabled) return;



            if (rectTransform == null)



            {



                rectTransform = GetComponent<RectTransform();



            }



            if (image == null)



            {



                image = GetComponent<Image();



            }



            if (image.type != Image.Type.Simple)



            {



                return;



            }



            Sprite sprite = image.overrideSprite;



            if (sprite == null || sprite.triangles.Length == 6)



            {



                // only 2 triangles



                return;



            }



            // Kanglai: at first I copy codes from
Image.GetDrawingDimensions



            // to calculate Image's dimensions. But now for easy to read, I
just take usage of corners.



            if (vh.currentVertCount != 4)



            {



                return;



            }



            rectTransform.GetLocalCorners(fourCorners);



            // Kanglai: recalculate vertices from Sprite!



            int len = sprite.vertices.Length;



            var vertices = new List<UIVertex(len);



            Vector2 Center = sprite.bounds.center;



            Vector2 invExtend = new Vector2(1 / sprite.bounds.size.x, 1 /
sprite.bounds.size.y);



            for (int i = 0; i < len; i++)



            {



                // normalize



                float x = (sprite.vertices[i].x - Center.x) * invExtend.x +
0.5f;



                float y = (sprite.vertices[i].y - Center.y) * invExtend.y +
0.5f;



                // lerp to position



                vertice.position = new Vector2(Mathf.Lerp(fourCorners[0].x,
fourCorners[2].x, x), Mathf.Lerp(fourCorners[0].y, fourCorners[2].y, y));



                vertice.color = image.color;



                vertice.uv0 = sprite.uv[i];



                vertices.Add(vertice);



            }



            len = sprite.triangles.Length;



            var triangles = new List<int(len);



            for (int i = 0; i < len; i++)



            {



                triangles.Add(sprite.triangles[i]);



            }



            vh.Clear();



            vh.AddUIVertexStream(vertices, triangles);



        }



    }



}

这个做法是用顶点数来换填充率,具体是否这么干还要看项目本身的瓶颈。这一点在官方论坛的帖子里我也和别人讨论过,这里就不再赘述了。

Unity-Profiler-window分析器窗口

Attaching to Unity players 附加到Unity播放器

To profile your game running on an other device or a player running on another
computer, it is possible to connect the editor to that other player. The
dropdown Active Profiler will show all players running on the local
network. These players are identified by player type and the host name running
the player “iPhonePlayer (Toms iPhone)”. To be able to connect to a player,
the player must be launched with the Development Build checkbox found in
the Build Settings dialog. From here it is also possible to tick a
checkbox to make the Editor and Player Autoconnect at startup.

要分析你的游戏运行在其他设备上或者在另一台计算机上运行的播放器,可以连接编辑器到其他播放器。Active
Profiler下拉菜单显示在本地网络上运行的所有播放器。这些播放器通过播放器的类型和运行播放器的主机名”iPhonePlayer(Toms
iPhone)”被识别。要能够连接到一个播放器,播放器必须在 Build Settings对话框中找到Development Build
复选框勾上的情况下打包生成。从这里也可以勾选一个复选框,使编辑器和播放器在启动时自动连接。

Profiler Controls 分析器控件

Profiler controls are in the toolbar at the top of the window. Use these to
turn profiling on and off, navigate through profiled frames and so on. The
transport controls are at the far right end of the toolbar. Note that when the
game is running and the profiler is collecting data clicking on any of these
transport controls will pause the game. The controls go to the first recorded
frame, step one frame back, step one frame forward and go to the last frame
respectively. The profiler does not keep all recorded frames, so the notion of
the first frame should really be though of as the oldest frame that is
still kept in memory. The “current” transport button causes the profile
statistics window to display data collected in real-time. The Active Profiler
popup menu allows you to select whether profiling should be done in the editor
or a separate player (for example, a game running on an attached iOS device).

分析器控件在窗口顶部的工具栏。使用这些控件打开和关闭分析,浏览分析好的帧等。传输控件在工具栏的最右端。请注意,当游戏运行、分析器收集数据时,点击任何这些传输控件(那两个小箭头)将暂停游戏。控件转到记录的第一帧,一步一帧向前(左箭头),一步一帧向后(右箭头),分别去到最后一帧。分析器不保留所有记录的帧,因此第一帧的概念,事实上应该是仍然保存在内存中的最旧的一帧。
“current”按钮会使得分析统计窗口显示实时采集的数据。激活分析器(Active
Profiler)弹出菜单让你选择是否应在编辑器或一个或独立播放器进行分析(例如,一个游戏运行在iOS设备)。

Deep Profiling 深度分析

When you turn on Deep Profile, all your script code is profiled - that is,
all function calls are recorded. This is useful to know where exactly time is
spent in your game code.

当你打开深度分析(Deep Profile),所有脚本代码将被分析 - 也就是说,所有的函数调用被记录。知道确切在你的游戏代码中花费的时间,这是有用的。

Note that Deep Profiling incurs a very large overhead and uses a lot of
memory, and as a result your game will run significantly slower while
profiling. If you are using complex script code, Deep Profiling might not be
possible at all. Deep profiling should work fast enough for small games with
simple scripting. If you find that Deep Profiling for your entire game causes
the frame rate to drop so much that the game barely runs, you should consider
not using this approach, and instead use the approach described below. You may
find deep profiling more helpful as you are designing your game and deciding
how to best implement key features. Note that for large games deep profiling
may cause Unity to run out of memory and so for this reason deep profiling may
not be possible.

注意深度分析(Deep
Profiling)会造成非常大的开销,并使用大量的内存,结果你的游戏在分析同时运行明显变慢。如果您使用的是复杂的脚本代码,深度分析可能不会完全有效。深度分析为使用简单的脚本的小游戏工作足够快。如果您发现您的整个游戏在深度分析时运行,导致帧速率下降很多,以至于游戏几乎不能运行,你应该考虑不采用这种方法,而是使用下面描述的方法。您可能会发现深度分析更有利于设计你的游戏,并确定如何最好地实现关键特性。注意深度分析,对于大型游戏可能会导致Unity耗尽内存,基于这个原因,深度分析未必有效。

Manually profiling blocks of your script code will have a smaller overhead
than using Deep Profiling. Use
Profiler.BeginSample
and
Profiler.EndSample
scripting functions to enable and disable profiling around sections of code.

手动分析脚本代码块比使用深度分析产生更小的开销。使用Profiler.BeginSample和Profiler.EndSample函数,启用和禁用分析代码段(从Profiler.BeginSample
到Profiler.EndSample间的代码)。

View SyncTime 查看同步时间

When running at a fixed framerate or running in sync with the vertical blank,
Unity records the waiting time in “Wait For Target FPS”. By default this
amount of time is not shown in the profiler. To view how much time is spent
waiting, you can toggle “View SyncTime”. This is also a measure of how much
headroom you have before losing frames.

当运行在一个固定的帧率或带垂直空白同步运行,Unity在”Wait For Target
FPS”记录等待时间,默认情况下,该段时间没有显示在分析器。要查看等待花费多少时间,您可以切换”View
SyncTime”。这也是衡量多少余量你之前丢失帧。

Profiler Timeline 分析器时间轴

The upper part of the Profiler window displays performance data over time.
When you run a game, data is recorded each frame, and the history of the last
several hundred frames is displayed. Clicking on a particular frame will
display it’s details in the lower part of the window. Different details are
displayed depending on which timeline area is currently selected.

分析器窗口的上部显示随着时间的推移的性能数据。当您运行游戏,每一帧数据被记录,最后则会显示几百帧的历史。点击一个特定的帧上,该帧的细节将显示在窗口的下部。具体取决于当前选定的时间轴区域显示不同的细节。

The vertical scale of the timeline is managed automatically and will attempt
to fill the vertical space of the window. Note that to get more detail in say
the CPU Usage area you can remove the Memory and Rendering areas. Also, the
splitter between the timeline and the statistics area can be selected and
dragged downward to increase the screen area used for the timeline chart.

时间轴的垂直刻度是自动管理,并尝试填补窗口的垂直空间。请注意,要获得更多关于CPU的使用率(CPU Usage)的细节,您可以删除内存(Memory
)和渲染(Rendering )区域。此外,时间轴和统计区域之间的分离器能被选择和向下拖动,为时间轴图表增加屏幕面积。

The timeline consists of several areas: CPU Usage, Rendering and Memory. These
areas can be removed by clicking the close button in the panel, and re-added
again using the Add Area drop down in the Profile Controls bar.

时间轴包括几个方面:CPU使用率,渲染和内存。这些区域可以在面板上按一下关闭按钮删除和 在分析控件(Profile Controls)工具栏中使用Add
Area 下拉菜单再次重新添加。

CPU Usage AreaCPU使用率区域

Rendering Area渲染区域

Memory Area内存区域

Audio Area音频区域

Physics Area物理学区域

GPU AreaGPU区域

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×