http://forum.china.unity3d.com/thread-13968-1-1.html

Unity有个消息系统,它可以在运行中当发生指定事件时调用你在脚本中定义的那些魔术方法。这是个非常简单和容易理解的概念,特别对新用户来说。只需定义一个这样的Update方法,就能在每帧调用它。

[C#]
纯文本查看 复制代码
void
Update() {
    transform.Translate(0, 0, Time.deltaTime);
}

对于一个经验丰富的开发者来说,这代码看起来有点奇怪。

        1.        不清楚这个方法究竟是如何被调用的。

        2.        不清楚当一个场景中有多个对象时,这些方法的调用顺序是什么。

        3.        这种代码风格无法使用Intellisense。

UPDATE是怎么被调用的

        Unity并没有使用System.Reflection在每次需要调用魔术方法时来定位它们。

        取而代之的是,在首次访问某个类型中的某个MonoBehaviour时,脚本运行时(Mono或IL2CPP)会检查其脚本中是否存在魔术方法,以及相关信息是否已被缓存。如果发现有已登记在册的特定方法,例如,如果一个脚本中定义了Update方法,它将被加入到一个需要每帧都更新的脚本列表中。

        在游戏过程中,Unity只是简单的循环迭代所有的列表然后执行其中的方法。所以,你的Update方法究竟是public还是private并不重要。

UPDATE方法们的执行顺序是什么

        执行顺序由脚本执行顺序设置(Script Execution Order Settings)(菜单:Edit > Project Settings > Script Execution Order)决定。要手动设置1000个脚本的执行顺序可不能不是什么好主意,但是要微调某个特定脚本的执行顺序还是可以的。当然,未来我们将会提供更加方便的方式来指定执行顺序,比如在代码中使用一个特性(Attribute)。

无法使用INTELLISENSE        

        我们在Unity中都使用某种类型的IDE编辑C#脚本,它们中的大多数并不喜欢那些完全搞不清何处被调用的魔术方法。这会导致警告以及代码导航困难。

        一些开发者用一个叫BaseMonoBehaviour或差不多名字的抽象类扩展MonoBehaviour,然后在他们的项目中的每个脚本里都扩展这个类。他们在其中写了一些有用的功能以及一堆虚魔术方法:

[C#]
纯文本查看 复制代码
public
abstract
class
BaseMonoBehaviour : MonoBehaviour {
    protected
virtual void
Awake() {}
    protected
virtual void
Start() {}
    protected
virtual void
OnEnable() {}
    protected
virtual void
OnDisable() {}
    protected
virtual void
Update() {}
    protected
virtual void
LateUpdate() {}
    protected
virtual void
FixedUpdate() {}
}

这个结构可以使你在代码中使用MonoBehaviour时更有逻辑性,但存在一个小缺点。我打赌你已经发现了……

        你所有的MonoBehaviour都会储存在Unity的内部更新列表里,你所有的脚本都会在每帧里调用所有这些基本上什么也没干的方法!

        有人可能会问为什么会有人关心一个空方法?因为这些从C++到托管C#的调用有成本上的开销。让我们来看看成本为何。

调用10000个UPDATE

        我为这篇文章在Github上创建了一个示例项目。它有两个场景,可以通过点击设备或在编辑器中按任意键互相切换:

        https://github.com/valyard/Unity-Updates

        (1) 在第一个场景中,使用下面这样的代码创建了10000个MonoBehaviour:

[C#]
纯文本查看 复制代码
private
void Update() {
    i++;
}

(2) 在第二个场景中,创建了另外10000个MonoBehaviour。不过,不同的是,这个代码中并不是只调用Update,而是像下面这样,加入了一个由Manager脚本在每帧都调用一次的自定义UpdateMe方法。

[C#]
纯文本查看 复制代码
private
void Update() {
    var count = list.Count;
    for
(var i = 0; i < count; i++) list[i].UpdateMe();
}

测试项目在两台iOS设备上被编译为Mono以及IL2CPP,发布设置中都设为非开发模式。它们的运行时间记录如下:

        1.        在第一次Update调用时设置一个Stopwatch (在Script Execution Order中配置)

        2.        在LateUpdate时停止Stopwatch

        3.        将获得的计时时间均摊到几分钟上

        

        Unity version: 5.2.2f1

        iOS version: 9.0

        

        

360反馈意见截图1652082594132115.png
(13.69 KB, 下载次数: 9)

下载附件

2016-1-14 11:25 上传

哇!好多时间!测试肯定哪里出了问题!

        实际上,我只是忘了把Script Call Optimization 设为Fast but no Exceptions,但是现在我们能看到这种设置对性能的影响了……至于IL2CPP不必太在意。

360反馈意见截图16501110628064.png
(15.12 KB, 下载次数: 10)

下载附件

2016-1-14 11:29 上传

OK,这样好多了,让我们切换到IL2CPP

360反馈意见截图164912178482124.png
(13.52 KB, 下载次数: 10)

下载附件

2016-1-14 11:31 上传

这里我们发现两件事情:

        1.        这个优化对于IL2CPP同样有用

        2.        IL2CPP仍有改进空间,而且在写这篇文章的同时Scripting 与IL2CPP团队正在努力提高性能。比如,最新的Scripting分支内包含的优化可以让测试运行快35%。

        我一会儿就会介绍Unity在幕后做了些什么,但是现在让我们修改下Manager代码,将它提速5倍!

接口调用,虚调用以及数组访问

        如果你还未读过这一系列有关IL2CPP的优秀博文,那么你应该在读完本文后马上去看看!

        结果告诉我们,如果你想在每帧里都循环迭代拥有10000个元素的列表,那应该使用数组而不是List,因为这样生成的C++代码会更简单,而数组访问就是要快很多的。

        在下一个测试中,我把List<ManagedUpdateBehavior> 改为了ManagedUpdateBehavior[]。

360反馈意见截图16450711655965.png
(16.73 KB, 下载次数: 10)

下载附件

2016-1-14 13:05 上传

这看起来好多了!

        我在Mono上使用数组运行时间是0.23ms。

        
解救之道!

        我们发现了从C++调用C#函数较慢,不过让我们再研究下当调用所有这些对象的Update方法时,Unity实际上做了些什么。最简单的方法就是使用Apple Instruments的Time Profiler。

        注意这不是Mono与IL2CPP 的对比测试 — 讨论的大多数内容对Mono iOS构建同样适用。

        我在iPhone6上用Time Profiler启动了测试项目,记录了几分钟的数据,然后选择了一分钟检视一次。从这行代码开始的所有东西我们都很感兴趣:

        void BaseBehaviourManager::CommonUpdate<BehaviourManager>()

        如果你以前没有使用过Instruments,右边是按照执行时间排序的函数,以及它们调用的其他函数。最左边的列是以毫秒为单位的CPU时间,以及这些函数及其调用的函数所占的CPU时间百分比。左边第二列是函数自己的执行时间。注意,在这个实验中Unity并没有将CPU使用完,所以我们能看到在60秒间隔内有10秒的CPU时间花在了我们的Update上。显然,我们关心的是那些执行时间最长的函数。

        我用我疯狂的Photoshop技术,将一些区域做了颜色区分,以便你能明白到底发生了什么。

360反馈意见截图16600901578958.png
(153.18 KB, 下载次数: 11)

下载附件

2016-1-14 13:08 上传

UpdateBehavior.Update()

在中间你能看到我们的Update方法,以及IL2CPP是如何调用它的 ——UpdateBehavior_Update_m18。但是Unity在那之前还做了很多其他事。

循环迭代所有的Behaviour

Unity循环迭代所有的Behaviour并执行更新。特殊的迭代类SafeIterator确保了即使移除了列表中的下一项,整个循环也不会中断。仅仅是循环迭代所有已注册的Behaviour就用了9979ms中的1517ms。

检测调用是否有效

下一步,Unity做了一堆检测,确保调用的方法是属于某个已激活已初始化且Start方法已调用过的GameObject的。你肯定不希望在Update里销毁一个GameObject时让游戏崩溃,对吧?这些检测花去了整个9979ms中的另外2188ms。

准备调用方法

Unity创建了一个ScriptingInvocationNoArgs实例 (代表了一个从原生到托管的调用)以及ScriptingArguments,然后命令IL2CPP虚拟机调用方法(scripting_method_invoke函数)。这一步消耗了整个9979ms中的2061ms。

Call the method

scripting_method_invoke函数检测传入的参数是否有效(900ms),然后调用IL2CPP 虚拟机的Runtime::Invoke方法 (1520ms)。开始时,Runtime::Invoke检测方法是否存在 (1018ms)。而后,它调用一个生成的RuntimeInvoker函数获取方法签名(283ms)。接着再依次调用我们的Update函数,根据Time Profiler,这一步花了42ms。

        一个漂亮的彩色表格。

360反馈意见截图16660114505663.png
(61.27 KB, 下载次数: 10)

下载附件

2016-1-14 13:14 上传

托管的更新

现在让那个我们在Manager测试上使用下Time Profiler。你在屏幕截图上可以看到,还是同样的一些方法(有些方法因为执行时间少于1ms,甚至都没出现),但是大部分的执行时间实际上都花在了UpdateMe函数上(或者说花在了IL2CPP调用它上——ManagedUpdateBehavior_UpdateMe_m14)。另外,IL2CPP还插入了一个null检测,确保我们循环迭代的数组不会为null。

        下面这个图片使用了相同的颜色。

360反馈意见截图16581117191561.png
(120.85 KB, 下载次数: 9)

下载附件

2016-1-14 13:16 上传

所以,你现在怎么看,我们应该忽略那小小的方法调用吗?

有关测试的几句话

        老实说,这个测试并不是完全公平的。Unity为了防止你的游戏出错或崩溃,做了很多了不起的事:这个GameObject是否已激活?它是否在Update循环中被销毁了?对象上是否存在Update方法?怎么处理在这个Update循环中创建的MonoBehaviour?——我的Manager脚本没有处理这其中任何一项,仅仅是循环迭代了一堆的对象,调用它们的Update而已。

        在真实世界中,Manager脚本可能会更加复杂,执行得更慢。但是,我是个开发者——我知道我的代码要做什么,我架构我的Manger类时,知道可能的行为是什么,什么不会出现在我的游戏中。而不幸的是,Unity并不知道这些。

你应该怎么做?

        当然这完全视你的项目而定,但实战中碰到一个游戏在单一场景中使用大量需要在每帧都执行一些代码的GameObject的情况并不少见。通常这看起来都是些不起眼的小代码,似乎不会影响到任何东西,但当其数量非常巨大时,调用几千个Update方法的开销将变得显著。这个时候再去修改游戏架构,重构这些对象为Manager样式,可能已经为时已晚。

        你现在有数据了,在你开始下一个项目时考虑下吧。

unity update优化的更多相关文章

  1. Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译

    本文是Unity官方教程,性能优化系列的第三篇<Optimizing garbage collection in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  2. Unity全面优化

    前言 Unity的项目优化已经是老生常谈,很多人在项目完成之后,即便创意新颖,也会觉得差强人意,原因就在于没有做详细的项目优化.众所周知,Unity是一个综合性的3D开发引擎,其中包含图像渲染,逻辑处 ...

  3. Unity内存优化

    [Unity内存优化] 1.在Update方法或循环中,少用string类,因为string类的每次操作都会调用new生成新字符串对象.用StringBuilder代替string,StringBui ...

  4. Unity 性能优化(力荐)

    开始之前先分享几款性能优化的插件: 1.SimpleLOD : 除了同样拥有Mesh Baker所具有的Mesh合并.Atlas烘焙等功能,它还能提供Mesh的简化,并对动态蒙皮网格进行了很好的支持. ...

  5. Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译

    本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  6. Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译

    本文是Unity官方教程,性能优化系列的第二篇<Diagnosing performance problems using the Profiler window>的简单翻译. 相关文章: ...

  7. Unity性能优化(1)-官方教程The Profiler window翻译

    本文是Unity官方教程,性能优化系列的第一篇<The Profiler window>的简单翻译. 相关文章: Unity性能优化(1)-官方教程The Profiler window翻 ...

  8. Unity内存优化(贴图层面)

    聊聊近况: 距离上一篇文章已经过了好久,主要原因是我懒了.公司项目也到了开始优化的阶段,上网找的资料,看过了就忘.还是想把它整理一下,写出来.其实我说的东西,网上都有,我只是搬运工而已. 贴图压缩: ...

  9. Unity内存优化技术测试案例

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

随机推荐

  1. ProgressBar+WebView实现自定义浏览器

    当我们使用浏览器浏览网页时,总会看到下图页面的样子,上面是一个地址栏,地址栏下面显示加载进度,加载完成后进入页面内容,带颜色的进度条总是少不了的,那样子看起来也舒服,如何实现自定义手机浏览器功能呢? ...

  2. 通过主机名来获取一个ip对象

    //通过名称(ip字符串or主机名)来获取一个ip对象. InetAddress ip = InetAddress.getByName("www.baidu.com");//jav ...

  3. ME01 创建货源清单function

    CALL FUNCTION 'ME_DIRECT_INPUT_SOURCE_LIST' Function module IDOC_INPUT_SRCLST FUNCTION IDOC_INPUT_SR ...

  4. 调用微信接口token的问题

    前言 微信的影响力众所周知,越来越多的人也都离不开它,工作,生活,社交的好帮手.相信大家对微信公众号,小程序也都不陌生,那么在开发公众号,小程序的时候需要调用到微信的接口,固然就会遇到token的问题 ...

  5. TCP服务器端和客户端程序设计【转】

    本文转载自:http://blog.csdn.net/yueguanghaidao/article/details/7035248# 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.实验目的 ...

  6. Log4Net的使用之winform

    当我们将asp程序部署到远程服务器上的时候,如果遇到程序错误,如何能快速的判断我们程序的错误呢.所以-->Log4Net作为记录日志的一大神器,不得不学会熟练使用啊!没有那么多的原理,照猫画虎的 ...

  7. spring的了解以及简单框架的搭建

    了解spring: Spring是一个开源的控制反转(Inversion of Controller)和面向切面(AOP)的框架,目的是为了简化开发. IOC(控制反转): public class ...

  8. 初入 CLR - 阅读《CLR via C#》笔记

    最近买了一本书<CLR via C#>阅读了第一章 - CLR 的执行模型,对 .NET 一直提到的 CLR 和 .NET Framework 有了一个大致的了解.我理解主要体现在: ■ ...

  9. 程序设计中的计算复用(Computational Reuse)

    从斐波那契数列说起 我想几乎每一个程序员对斐波那契(Fibonacci)数列都不会陌生,在很多教科书或文章中涉及到递归或计算复杂性的地方都会将计算斐波那契数列的程序作为经典示例.如果现在让你以最快的速 ...

  10. 主库报 Error 12154 received logging on to the standby PING[ARC2]

    主备网络配置存在问题 一系列报错 [root@node1 bin]# ./srvctl  start database -d devdbPRCR-1079 : Failed to start reso ...