Unity を検索

最適化の最前線から:Unity 2020 LTS でのマネージコードストリッピングの強化

2021年10月25日 カテゴリ: テクノロジー | 10 分 で読めます
Population one
Population one
取り上げているトピック
シェア

マネージドコードストリッピングは、アプリケーションのバイナリファイルのサイズを小さくするための、ビルドプロセスにおける重要なステップです。これは未使用コードを削除することで実現されます。 

コードを削除することで、そのコードがコンパイルされたり最終ビルドに含まれたりしないようにします。これにより、IL2CPP バックエンドを使用しているプロジェクトのメモリ使用量はわずかに減少しますが、マネージコードのストリッピングのレベルを高くすることには、実行時に型やメソッドが見つからなくなる(その他の問題も発生する)リスクが常にあります。

ビルドプロセスで、使用されていないと判断されたコードは、結果的に削られることになります。必要なアセンブリを手動で link.xml ファイルに追加することは、削除されないようにするための最も簡単な方法ではありません。私が Unity ソフトウェア開発コンサルタントとして行っているプロジェクトレビューの中で、マネージドコードストリッピングをどのように扱えばよいかという質問を顧客から受けたことがあります。そこで、マネージドコードストリッピングに関する新しいアノテーション属性を活用してワークフローを改善するためのヒントとベストプラクティスを集めました。

Unity リンカーを使ったマネージドコードストリッピング

未使用コードを削除することは、IL2CPP スクリプトバックエンドを使う時に特に重要です。Unity リンカーは、Mono IL リンカーを Unity で動作するようにカスタマイズしたもので、マネージドコードを取り除くための静的解析を行います。

Unity では、IL2CPP のマネージドコードストリッピングについて、Low、Medium、High の 3 つのレベルをサポートしています。マネージドコードストリッピングのマニュアルでは、コードストリッピングプロセスの仕組み、特定のコードが削除する要因、ストリッピングレベルの違いなどを説明しています。要するに、コードストリッピングのレベルが高ければ高いほど、リンカーはより積極的に使われていないコードを見つけて削除しようとします。マネージドストリッピングレベルは、プロジェクトの Player Settings で変更できます。

リンカーが使用されていないコードを特定するために活用している静的解析は、実行時にしか定義されていないオブジェクトの型がある場合、すべてのケースをカバーすることはできません。このようなケースでは、「偽陽性」となってしまいます。コンパイル時にはクラスやメソッドへの参照はないかもしれませんが、実行時にはコードの一部でそのクラスやメソッドが必要になります。この文脈では、リフレクションを使用したコードが良い例となります。次のようなスニペットを考えてみましょう。

これは正しく、かつよく使われるタイプのコードですが、リンカーは MyAssembly、MyType、MyMethod が実行時に実際に使用されているかどうかを知りません。このため、これらのコードが削除されることがあり、ひいては実行時エラーが発生する可能性があります。詳しくは、スクリプトの制限のマニュアルをご確認ください。

Zenject のような依存性注入フレームワークや、Newtonsoft.Json のようなシリアライゼーションライブラリを使用している開発者は、「偽陽性」のコードストリッピングが発生する可能性があることを認識し、対処しなければなりません。ここでは、代表的なアプローチをご紹介します。

  • リンカーは数多くの属性を認識します。そのため、リンカーが依存関係を特定できない場所にアノテーションを付けることができます。そのため、削除してはいけないアセンブリ、クラス、メソッドに [Preserve] 属性を追加することができます。
  • link.xml ファイルはプロジェクトごとに用意されるリストで、アセンブリ、型などのコードエンティティをどのように保存するかを記述したものです。必要なアセンブリ、クラス、メソッドを手動で link.xml に追加するか、UnityEditor API の GenerateAdditionalLinkXmlFile を使用して、ビルドプロセス中に link.xml ファイルを生成することができます。

Addressables パッケージでも、LinkXmlGenerator を利用しています。Addressables のビルドスクリプトは、グループ内のアセットのリストを確認し、アセットが使用する型を link.xml ファイルに追加します。また、実行時にリフレクションによって Addressable が内部で使用する型を追加します。デフォルトのビルドスクリプトである BuildScriptPackedMode.cs には、スクリプタブルレンダーパイプラインのように、ビルドプロセスのステップとして同様のソリューションの実装に関する詳細が記載されています。

Unity は Assets フォルダーまたはそのサブフォルダー内にある複数の link.xml ファイルをサポートしています。ビルドプロセス中に、複数の link.xml ファイルのエントリがマージされ、リンカーはその内容を考慮します。

Unity 2020 LTS でのマネージドコードストリッピングの新機能

[Preserve] 属性を使用するには、手動での作業が必要な場合があります。しかし、皆さんがすでに Unity 2020 LTS でプロジェクトを開発している場合、いくつかの新しいマネージドコードストリッピングのためのアノテーション属性を使って、コードストリッピングで削除すべきでないアセンブリ、クラス、メソッドを簡単かつ正確にマークすることができます。ここでは、その一部をご紹介します。

  • RequireAttributeUsagesAttribute:ある属性の型がマークされると、その型のすべての CustomAttribute もマークされるので、ストリッピングレベルを High にしている時の作業の煩雑さが軽減されます。
  • RequireDerivedAttribute:ある型がマークされると、その型から派生したすべての型も同様にマークされます。
  • RequiredInterfaceAttribute:型がマークされると、指定された型のすべてのインターフェース実装がマークされます。
  • RequiredMemberAttribute:型がマークされると、[RequiredMember] を持つ型のすべてのメンバーがマークされます。これにより、宣言した型が削除できなくなる状況を防ぐことができ、コードストリッピングの精度がより向上します。ただし、クラス自体が使用されていない場合、[RequiredMember] 属性でマークされているにもかかわらず、メンバーも削除されてしまうことに注意してください。
  • RequireImplementorsAttribute:型がマークされると、指定された型のすべてのインターフェース実装がマークされます。そのため、すべての実装をマークする必要はありません。しかし、そのインターフェイスがコードベースのどこにも実装されていない場合は、 [RequireImplementors] 属性でマークされていても削除されてしまいます。

Unity 2020.1 と 2020.2 では、ツールに API の更新が施され、Mono IL リンカーと同等の性能を持たせています。いくつかのシンプルなリフレクションパターンを検出できるようになりました。つまり、Unity 2020 LTS にアップグレードした場合、link.xml ファイルを使用する理由が減るということです。

Unity 2020 LTS によるコーディングワークフローの最適化については、Unity 2020 LTS マニュアルのこの機能の概要アップデートページ をご覧ください。

今後の予定

テスターやプレイヤーに高品質なビルドを簡単に提供するという 2021 年の目標の一環として、コードストリッピングのワークフローの改善に注力してきました。具体的には、2021.2 のリリースでは、Minimal という新しいマネージドストリッピングレベルを追加しました。これは IL2CPP バックエンドのデフォルトとなります。今後の情報にもご注目ください。

Managed Stripping Level
マネージドストリッピングレベルのプロパティの新しいオプションに関する今後の情報に注目しよう。
2021年10月25日 カテゴリ: テクノロジー | 10 分 で読めます
取り上げているトピック