Unity最近发布了Burst 1.5。新版本重点添加了多条Neon intrinsics指令。Neon intrinsics指令支持精确设定矢量命令,为Arm CPU的处理进程生成最为高效代码。Neon指令集传统上只适用于C/C++语言,而Unity目前已成功将其移植到了C#中。
不过,要找到最合适的指令并没那么简单,Arm为此特地制定了一份“Neon intrinsics在Unity中的运用”指南,外加一个带有开源代码的Unity项目,供广大开发者参考。这篇指南将帮助你以正确的结构构建Burst代码,让代码自动应用Neon指令集,享受其带来的性能提升,为你省去自行编写指令集的麻烦。我们下面就来看看怎样才能充分利用起Neon intrinsics。
Burst编译器将自动采用Neon intrinsics来提高性能收益,而我们仍可以使用其它方法来进一步提高Burst性能。举例来说,我们可根据一定的结构来调整数据和函数循环结构,利用自动矢量化来获取大量的性能提升。
要想在Burst编译器中将四条指令合并为一条Neon SIMD指令,我们可编写简短、连贯并保留内联的函数。此外,经我们在物理碰撞模拟中测试发现,传入Burst函数的指针在添加[NoAlias]属性后,其速度可提高4倍。
本文中的案例重点在于物理碰撞,因此上方演示场景仅使用了简单的胶囊型和立方体图形。例中对两种类型的碰撞进行了优化:用于角色-墙体间碰撞的轴向边框盒(Axis-Aligned Bounding Boxes,AABB),及用于角色之间碰撞的截面半径碰撞。
手动编写的指令要快于编译器并不简单,但这里将介绍几种方法。性能改进不仅是个目标,也是一个过程。在进入分析优化阶段后,我们可以先分析线程的运行耗时,再进行调整,如此循环往复。我们能使用Profile Analyzer,或自己的计时方法来完成这一阶段的优化。
这时我们需要将注意力放到代码调整上。在本例中,我们将原先的Burst jobs代码转写成了静态函数,方便进行计时。异步执行在最终的游戏代码中发挥了巨大作用,尽管性能计时使得代码执行更为复杂了一点。在一个真正的游戏里,你需要使用ProfilerMarker、ProfilerRecorder和ProfileAnalyzer来计算job的耗时。而此例中,改写后的Burst静态函数实际上强制在脚本中应用了自动矢量化结构。如果job使用的是由Burst静态函数组成的NativeArray,则基本类型的指针使用起来也会更加简单,静态函数会把数据拆分成更易矢量化的片段。而应用在指针上的[NoAlias]属性会告诉编译器,指针调用的数据是否有重复。在本例中,Burst的性能非常强大,以至于只有及其出色的Neon代码才能比它更快。为了充分发挥Neon的作用,这两种类型的碰撞都需要采用特定的数据和逻辑结构。
矢量化的效果在同时比对四个或八个对象时最好,因为这时运算可一次性完成(有正确的Neon指令的情况下)。本篇指南将介绍几种维持最大性能的实例。
要想了解完整的优化过程,查看实例的实际效果,学习在自己的项目中进行应用,请在这里阅读应用指南,在此处查看优化后的代码。新指令的效果可谓立竿见影,其中一项优化甚至能让Burst代码比结构精巧的非Burst代码快6倍,让手动编写的Neon代码快10倍。
有关Neon的详细信息,请访问Arm Neon开发者页面。虽然网站的大部分内容是针对C/C++指令的,但同样的原则也适用于C#。另外,请在这里查看Unity支持的Neon intrinsics指令列表及Arm的Neon instrinsics搜索引擎。
本博客由Arm行业推广大使(Developer Advocate)Ben Clark共同撰写。
Is this article helpful for you?
Thank you for your feedback!