Unity を検索

Unity Burst での Neon intrinsics の使用に関する新しいガイド

2021年7月30日 カテゴリ: ゲーム | 8 分 で読めます
Header image showing someone working in Unity
Header image showing someone working in Unity
取り上げているトピック
シェア
Example chart of neon intrinsic multiply accumulate
図 1:Neon intrinsic で積和演算を行う例

Unity が最近リリースした Burst 1.5 で注力した部分の 1 つに、Arm 社の Neon intrinsic の追加があります。Neon intrinsic を使うと、正確なベクトル命令を指定して、Arm CPU 上のワークロードを処理するための最も効率的なコードを得ることができます。こうしたコードは通常 C/C++ で書きますが、Unity では C# で書くことができます。

しかし、目的に合った最適なコマンドを理解するのは難しいので、Arm は、Neon intrinsics を Unity で使用するためのガイドを作成しました。 、コードが利用可能なUnity プロジェクトを添付しています。 。このガイドでは、Neon を自動的に使用するための Burst コードの構造を説明します。Neon を使うと人手で複雑な intrinsic を書く労力を払わなくても大きなパフォーマンス向上を実現することができます。Neon intrinsic を最大限に活用するためのベストプラクティスを見ていきましょう。

自動ベクトル化

Burst コンパイラーでは、パフォーマンスを最大限に引き出すために、手動で intrinsic の記述を行う必要はありません。しかし、Burst が処理しやすいようにコードを書くことで、さらにパフォーマンスを引き出せる場合があります。たとえばデータやループの構造を調整することで自動ベクトル化を容易にし、それに伴う大きなパフォーマンスの向上を図ることができます。

コンパイラーが 4 つの命令を 1 つの Neon SIMD 命令に変換するようにするには、短くて単純なループを作り、区切りを入れずに、関数はそのままにしておきます。さらに、Burst 関数に渡されるポインターに [NoAlias] 属性を使用すると、物理演算の衝突に関するケーススタディで示されているように、処理速度が 4 倍になります。  

Demo scene of neon intrinsics
図 2:デモシーン

開発者から寄せられた事例

このサンプルケースは、物理演算の衝突をテーマにしているため、上の画像に示したように、何の変哲もないカプセルとキューブで画面は構成されています。ここでは 2 種類の衝突の最適化が行われています。キャラクターと壁の衝突には軸並行境界ボックス(AABB)が、キャラクターとキャラクターの衝突には半径ベースの衝突判定が用いられています。

コンパイラーによる最適化より効率のいい intrinsic を使った処理を手で書くのは簡単ではありませんが、このケーススタディではそれをやるためのアプローチをいくつかご紹介します。パフォーマンスの向上は単なる目標ではなく、プロセスです。測定して最適化するサイクルができたら、プロファイルを作成して処理にかかる時間を確認し、修正を加えて再び時間を測ります。この時間計測は Profile Analyzer を使うか、独自の時間計測処理を入れて行うことができます。

ですので、修正に専念すればいいことになります。ケーススタディでは、Burst ジョブから Burst の静的関数に移行したことで、時間計測を行いやすくしました。完成版のゲームを作る時は、パフォーマンスの計測が一段複雑になるものの、非同期性は大きな武器になります。実際のゲームでは、ProfilerMarker、ProfilerRecorder、ProfileAnalyzer を使ってジョブ内の時間を計測します。しかしここでは、Burst の静的関数への移行により、自動ベクトル化に必要な変更が行われるようにしました。構造体の NativeArray を Burst の静的関数で使うようにジョブを設定した場合、基本的な型にポインターを使用することはそれほど複雑ではありません。これにより、データはより簡単にベクトル化できるように分割されます。そして、[NoAlias] 属性がポインターに追加されると、ポインターが使用されていたデータに重複があったかどうかがコンパイラーに伝えられるようになります。今回の事例では、通常の Burst の性能が非常に高く、これを超える性能を出すには、非常に優れた Neon コーディングを行う必要がありました。Neon をフル活用するためには、2 つの異なる種類の衝突について、それぞれデータとロジックの適切な構造化を行う必要がありました。

ベクトル化は、4 つまたは 8 つのオブジェクトを同時に比較できる場合に最も効果を発揮し、これらのオブジェクトに対して同じ操作を一度で完了させます(適切な Neon 命令を使う必要はあります)。最新のガイドでは、最高のパフォーマンスを維持するための例を紹介しています。

壁の衝突のサンプルで、AABB のコードを比較している部分を見てみましょう。

普通のコードで直接書いた場合

Burst の静的関数を使って呼び出す場合

Neon intrinsic 命令で書かれた Burst 静的関数を使う場合

chart showing neon is 10X faster in AABB collisions and 3.5X faster in arm neon

完全版のガイドを見る

このプロセスの全体に興味がある方や、実際の例を見て自分のプロジェクトにどのように適用できそうか検討したい方は、ガイドを読み、指定されたコードをご覧ください。とある最適化の事例では、上手く記述された非 Burst コードに比べて Burst コードを導入すると 6 倍、人手で書かれた Neon コードを導入すると 10 倍高速化したという報告もあり、確かな効果があることがわかります。

Neon についての詳しい情報は、Arm 社の Neon マイクロサイトをご覧ください。そこで紹介されている内容の多くは C/C++ で書かれる intrinsic を扱うものですが、同じ原理が通用します。さらに、Unity の出している実装済み Neon intrinsic のリストや、Arm のNeon intrinsic 検索エンジンもぜひご覧ください。

 

このブログは、Arm 社のデベロッパーアドボケイトである Ben Clark 氏と共同で執筆しました。

2021年7月30日 カテゴリ: ゲーム | 8 分 で読めます
取り上げているトピック