Rider Tutorials 开发工具

以 Rider 实现 Unity 性能的最佳实践(一)

在 JetBrains,我们是静态分析的狂粉。Rider 内含 1,200 多个检查,旨在警告潜在的问题,或提供改变建议,几乎所有都有快速修复以解决问题。有的是关于一致性,比如命名表彰和代码风格

其他警告冗余、未使用或不必要的代码,或帮助您编写简要、更现代的代码,利用新的语言功能。当然,Rider 将提醒您代码质量问题,比如潜在的空引用异常或其他运行时问题,比如无限循环。Rider 甚至还有一些Unity 专用检查和快速修复 – 比如提醒关于在 Unity 项目中模棱两可地使用 ?? 和 ? 操作符,Unity 的魔术方法的错误的方法签名等等。

但如果 Rider 能够帮助所有 Unity 开发人员都关心的一件事情 – 性能,是不是很不错?

惊喜!当然可以!

我们可以将 Rider 的性能指南划分为两个类别,而且我们将在两篇文章中分别探讨它们。下次,我们将看看更传统的检查 – 突出显示冗余事件功能等问题作为警告,并提供机械修复。但在本文中,我们将谈谈 Rider 2018.3 引入的一项功能,性能指标和性能关键型上下文。

  • 了解性能指标

  • Unity 专用性能检查

具体是什么?

Rider 知道 Unity 有很多经常调用的方法,比如每个帧,而这些方法被视为性能关键型上下文(performance-critical context)而处理,并且在编辑器边槽中高亮显示。一旦进入这个关键型上下文,Rider 将高亮显示任何已知的代价高昂的 Unity API 调用

重要的是,这些是典型的需要修复的警告,但使用不同实心下划线来提供信息和认知。

这一功能背后的观点是,并非所有 Unity 的性能最佳实践都适用于传统警告和快速修复

 

例如,在Update方法中使用SendMessage会导致性能低下,因为SendMessage 必须使用反射来查找方法,根据名称或任何附件MonoBehaviour ,不是你想对每帧做的事情。Rider 可以在此轻松添加警告突出显示,但修复则会困难些 – 最终,它需要避免  SendMessage  一起,并且‘重新架构’某些代码。

类似地,在某些情况下,您写的代码并未达到最佳性能,但已足够实用了。它也许是菜单或某些不太关键的功能的代码。提高性能是一个好主意(别忘了电池续航时间!),但是修复此问题的优先级可能低于当前其他任务。

这意味着您被代码中无法轻易修复的警告缠住了,或者暂时没时间修复问题。我们觉得那是一种糟糕的用户体验 – 我们不想鼓励您忽略警告!我们非常乐意为您配置或明确并有意识地压制个别警告,但我们认为留下很多未处理的警告是很差的体验。

所以我们引入了性能指标这个概念(performance indicators)。与显示不能帮助修复的传统警告相比,我们使用不同的高亮显示、实线下划线,以便您知道自己正在调用昂贵的方法。您现在可以做出明智的决定,确定要修复的内容、方式和时间,而不会被大量警告困扰。这与堆分配查看器 (Heap Allocations Viewer) 插件的方法类似,因为它突出显示分配和装箱。它不会告诉你分配不好,但让你知道它们发生在哪里。

由你来决定如何处理这些信息。

还有更多!

现在我们已经看到了它的样子及其背后的想法,让我们深入研究一些细节。Unity 有很多非常频繁调用的方法。例如,MonoBehaviour.Update 被每帧调用,就像LateUpdate ,和FixedUpdate 可以在一帧中多次调用。Rider 把所有这些方法视为性能关键型(performance-critical),而且将在编辑器边槽中高亮显示方法。

Unity 还有协同程序(coroutines)的概念,可以产生执行并在以后完成的迭代器方法。这允许函数 co-operatively 运行的时间比单个帧长, 而不会导致帧速率下降。Rider 将识别协同程序方法,这些方法传递到StartCoroutine ,作为字符串文字或者也是性能关键型的直接方法调用。

更好的是,Rider 将性能关键型上下文传播到从另一个性能关键型上下文方法调用的同一文件中的任何方法。

一旦进入性能关键型上下文,Rider 就启用大量检查:

上面的链接将带您进入每个检查的文档页面,其中详细说明了高亮显示方法调用的原因,以及可以采取哪些措施来避免。可以直接从 Rider 获得该文档,就像很多其他检查一样,使用“为何 Rider 如此建议? (Why is Rider suggesting this?)” Alt+Enter 菜单项。

我们来看一个可能令人惊讶的例子 – 将 Unity 对象与null 比较.您可能没想到这是性能问题,但 Unity 对象会覆盖相等运算符,并在检查null 时,将调用本机代码来查看底层引擎对象是否仍然存在(可以在这篇 Unity 博客文章中找到更多详情)。这种转换回 Unity 的本地部分需要付出代价,因此 Rider 强调这是代价高昂的操作。

这是我们使用指标而不是传统警告的一个好例子。这是一个完美的有效性检查 – 但它代价很高。Rider 无法知道此对象的预期生命周期,因此无法知道是否应该检查。因此,Rider 只是告诉您,您正在执行高代价的操作,而由您决定应该如何处理。

如果您知道该对象的生命周期较长,则可以愉快地删除该代码。如果您知道可以破坏对象,则可以将所有内容保留原样 – 编辑器滚动条中的错误条带中没有警告,或者在解决方案级分析中报告。如果您愿意,可以通过 Alt+Enter 菜单用评论来抑制突出显示。

我们来看看另一个稍微臭名昭着的例子,使用Camera.main  而不是性能关键型上下文/。这很影响性能,因为实施Camera.main 不使用缓存值,但总是调用FindGameObejctsWithTag ,这是个昂贵的方法调用,特别是每帧都调用时。这与“避免使用Find  方法”检查的影响基本相同,应该避免。

Rider 也可以在这里帮上忙,通过提供两个上下文操作,将创建一个字段并且在Start Awake 中初始化该字段。但请注意,这会更改方法的语义 – 如果确实改变了主摄像头,缓存的值不会更新。这是否是一个要求由您自行决定。

并且锦上添花的是,这可以跨同一个文件中的方法。性能关键型上下文沿调用链传播至直接和简介调用方法。并且知道,执行昂贵操作的方法在调用链上传播回初始性能关键型上下文。这意味着如果Update 调用了某方法(同一文件中的),而该方法间接调用GetComponent ,Update 中调用的方法被标记为昂贵

希望这些性能指标能够提供很多好处,不是通过告诉您代码何时需要修复,而是通过提供关于何时在性能关键型上下文中进行昂贵调用的意识。当然,如果您不想要它们,那么可以在Preferences(偏好) | Editor(编辑器) | Inspection Settings(检查设置) | Inspection Severity(检查严重程度) | C# 设置页面的 Unity 部分禁用单个检查,或者在 Unity 设置页完全禁用它们。

下次,我们将看看 Rider 提供的性能指南作为更传统的检查和快速修复。在那之前,确保您已下载了最新版本并且试试 Rider

在我们开始之前,指出 Rider 的分析对于正常的性能分析的作用无可替代是非常重要的。这些功能旨在帮助您在与 Unity API 交互时遵守 Unity 的最佳性能实践,并且不要尝试在代码的其他方面找到性能问题。

 

切记三思而后行!

博客文章以 Rider 实现 Unity 性能最佳实践 (Unity Performance Best Practices with Rider),第一部分首次刊登在 .NET 工具博客上。

原文发表于2019年2月21日,作者 Matt Ellis

image description

Discover more