Unity Profiler 真机调试以及函数耗时案例分享

发表于2018-11-10
评论6 1.19w浏览
最近实在是太忙了,一直想保持定量的更新频繁,但无奈当下的工作量实在太多了,只能先去处理手头上的工作,趁周末将最近应用Profiler工具解决的一个关于函数耗时的案例分享出来 

有些日子没做优化相关的工作,Profiler也许久没有使用,有些生疏,所以文中有误的地方,还请指正

下面先说一下如何使用Unity Profiler在真机上进行调试,我在项目中使用的Unity版本是5.6.5p4

UnityProfiler官方指南
https://docs.unity3d.com/Manual/Profiler.html

连接真机调试的方式有两种:

1、WIFI--保证PC和手机在同一个网络中

2、ADB--连接上手机,通过ADB命令连接

两种方法都OK,我在测试的时候,两者同时使用,如果网络不太稳定,我会选择ADB的形式

第一种方式:WIFI

1) 保证手机正确的连接到电脑,在Android Studio的Terminal下输入命令:
->adb devices

List of devices attached

xxxxxxxx device

说明手机已经连接成功,如果连接不成功,可以Kill掉adb或是重启Android Studio,还有一种情况可能会连接失败,就是ADB的版本太低,升级后尝试

如果我启动了模拟器,会输出如下:
List of devices attached
xxxxxxx        device
emulator-5554   device

这时候使用adb命令的时候,要记得指定设备

如果 adb命令不识别,需要将adb的路径加入到环境变量中。




(这里再吐槽一句,之前我在使用Android Studio时,一直因为墙的原因,导致很多库无法更新下来,即便是开了VPN,也会出现问题,公司里的网络默认是通过VPN进行访问的,速度很快,
Android Studio也非常的流畅的更新了下来,VPN和VPN之间还是有蛮大的区别的,当时为省钱
去用一些便宜的VPN,导致给自己添了很多的麻烦,在效率面前,多花钱能提高的,都是值得付出的)

2)通过adb判断真机正确连上机器以后,下一步设置Unity工程(前提保证可以正常的导出APK,SDK,JDK,NDK正确配置)


这里使用Gradle来进行构建,并选中Development Build和Autoconnect Profiler

打开Player Settings:


勾选Enable Internal Profiler

然后点击Build And Run,构建完成,打开Unity Profiler,等待真机启动,
稍等片刻,Unity Profiler就可以获取真机上的性能数据了。


点击Active Profiler可以看到当前连接的真机Xiaomi_MI_NOTE_Pro@xxx


网络环境不好的情况下,这里会出现延迟,即Unity Profiler捕获不到真机的数据,也没有可用的Active Profiler,可以稍等片刻,如果一直没有,重启Unity:)

在测试中,可以通过点击Record来进行记录(默认是开启的),再次点击可以取消Record,这样我们可以针对之前Record的数据进行分析

第二种方式 :ADB

看上图,Active Profiler->AndroidPlayer(ADB&127.0.0.1:34999)这是通过ADB的形式连接真机调试,如果没出现这个选项,可以在Terminal下输入adb命令:

adb forward tcp:34999(上图显示的端口号) localabstract:Unity-包名

这里要注意防火墙是否屏蔽掉了54998~55511端口,最好关闭防火墙。

在实际的测试过程中,比如我要测试某个场景的Profiler性能数据,方便我们操作和定位,可以先增加一个空的Scene,然后启动后,确保Profiler已经连接上真机了,待命状态,这时候,手动的点击按钮切换到我们需要测试的Scene下

以上就是Unity Profiler连接真机调试的操作过程,下面分享一个最近通过Unity Profiler定位到的一个函数耗时的案例

场景:
接手CP的一个项目,发现在中低端机上,加载耗时过长,通过定位发现其中一个Http请求的操作引起GC Alloc并耗时过长(测试的机器是小米Note,性能尚可,所以显示的消耗并不大,在中低端设备上消耗比较明显)。

我对项目代码并不熟悉,时间又比较紧迫,所以只能通过Profiler采样,一步一步的缩小问题的范围,直到找到消耗热点的最终位置。

下面是在第一个场景在加载时,Profiler的情况:


右上角的蓝色区域是消耗峰值,我们用鼠标定位当前的帧(用键盘更好定位),会在Overview下列出当前帧的函数消耗,我们点击GC Alloc进行排序,发现HttpUpdateDelegator.Update()占了79.6%的消耗,而当帧的总消耗是83.1%,所以问题直接就可以定 位到HttpUdateDelegator.Update()方法中。

打开HttpUpdateDelegator.cs发现,这是Best Http(pro)插件的源码,所以初步确认,这种消耗应该不是插件本身的,因为BestHttp在许多项目中应用过,性能缺陷不至于在这里,应该是CP使用上的问题,继续定位到Update方法:


在Update方法中,可以看到最下面那行代码:
HttpManager.OnUpdate()

所以我们继续进入这个OnUpdate方法中:

OnUpdate代码太多,贴上来太影响阅读,贴上一个简图:

在HttpManager.cs中,维护一个当前活动连接对象的List:

private static List<ConnectionBase> ActiveConnections = new List<ConnectionBase>();

在OnUpdate中遍历ActivieConnections,并对不同的HTTPConnectionStates状态进行处理,因为
无法确定具体是哪些case的消耗,所以我们可以对每一个case进行采样,以获取更准确的数据。

比如像下面这样:

这样我们再次打包在真机上进行Unity Profiler调试。


经过测试,最终问题定位在HttpUpdate_WaitForRecycle这个采样下,在WaitForRecycle状态下只有几行代码:


我们可以分别为三个方法,再继续进行采样,最终问题定位在:conn.HandleCallback()这里:


对HandleCallBack继续通过采样定位,最终定位在CurrentRequest.CallCallback()这一行,
这是一个Delegate,性能的消耗是在这个Delegate中。

所以,我们只需要知道,这个Delegate是哪个类中的哪个方法来实现的就可以了。

通过Debug调试,最终确定如下:


在TimeController类中实现了Delegate,最终性能消耗点定位如下:


将Http请求回来的时区数据(数据量特别小),通过Newtonsoft.Json库进行反序列化。

Newtonsoft.Json是第三方的Json解析库

https://www.newtonsoft.com/json/help/html/N_Newtonsoft_Json.htm

Newtonsoft.Json在移动端消耗太大,我们可以使用Unity提供的JsonUtility替代Newtonsoft.Json即可。

只改一行代码即可:
_res = JsonUtility.FromJson<TimeZoneResponse>(_response.DataAsText);

这样我们再打包进行Unity Profiler调试,性能热点消失,GC Alloc为0,Time ms 小到可以忽略不计。

这里还有一点需要注意,最终定位到Callback,因为是Delegate,所以这里不一定只有TimeController一处,也可能会被其它的逻辑处理(我在后面又发现了2次相同的情况),并使用到了Newtonsoft的Json库,所以这种情况要再仔细的确认OK

这个项目中大量的使用到了newtonsoft,全部替换掉需要不少的工作量,CP采用这种方式可能是因为原生JsonUtility不直接支持List和Dic的序列化,不过也是有办法可以解决的

常见问题:
1.如果发现Unity Profiler连接不上真机,请查看PlayerSettings中打出来的是不是
Development Build版本并且勾选了Autoconnector Profiler,在PlayerSettings中,也要记得开  启是Release是不可以的

2.连接方式有ADB和WIFI两种,ADB更稳定,AndroidPlayer@xxxx:xxx就是ADB连接的形式,确保了上面问题1的情况OK,还是无效,建议重启Unity试试

最近在研究冷启动(cold start),也是比较有趣的地方,稍后会分享上来。

分享就到这里,感谢您的阅读,周末愉快~

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