【Unity】Assembly Definitionの参照方法によるコンパイル時間の違いについて調査してみた

**[Assembly Definition Files](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html)を導入する/している方向け**の小ネタです。
※`Assembly Definition Files`だと長いので記事中では**`ADF`**と表記

`ADF`を導入して開発を行っていると、**`ADF`の参照方法によって**体感的にコンパイル時間の違いを感じることが有りました。 (私だけ..?)
参照方法に紐づくコンパイル時間については以下のように分類できそうな印象があります。

- **A: (Bと比べるとコンパイル時間が長い？)**
    - → `Assets`以下にあるスクリプトを`ADF`で分割して参照する時
    - → [ローカルにあるパッケージを登録](https://docs.unity3d.com/Manual/upm-ui-local.html)してパッケージ内の`ADF`を参照する時
- **B: (Aと比べるとコンパイル時間が短い？)**
    - → [公式パッケージをインストール](https://docs.unity3d.com/Manual/upm-ui-install.html)してパッケージ内の`ADF`を参照する時
    - → [自作パッケージ](https://docs.unity3d.com/Manual/CustomPackages.html)をリポジトリ[^1]から直接取得してパッケージ内の`ADF`を参照する時


そしてこれらの参照方法の挙動を見た感じだと、内部的に`.csproj`が生成されるかどうかの条件にもなっているように思われました。簡潔にまとめると以下のように分類できそうです。

- Project以下及びローカルパッケージにある`ADF`を参照
    - **→ `.csproj`が生成される**
- 公式レジストリ/リポジトリから取得してパッケージ内の`ADF`を参照
    - **→ `.csproj`は生成されない**
    - **取得したパッケージそのものは`Library/PackageCache`以下にキャッシュされる** [^2]

この挙動の違いがコンパイル時間に影響を及ぼしている?と予想して簡単に調査の方を行ってみました。
まだ分からない点や未調査項目がチラホラと残っており...ほぼ個人メモの公開みたいな内容にはなりますが、一先ずは現状見えている範囲についてだけ纏めていきます。
(間違いとかあればコメントなりTwitterなりで直接教えて頂けると幸いです :bow:)

[^1]: GitHubの公開リポジトリとか
[^2]: もう少し正確に言うと[Global Cache](https://docs.unity3d.com/Manual/upm-cache.html)にもキャッシュされるっぽい



#### ※注意点

**※この記事中では`ADF`に関する基本的な所は解説しません。**
基本的な情報については以下の記事がおすすめです。

- [【Unity】「AssemblyDefinitionを分けるとコンパイルが高速になる」ってホント？検証してみた！](https://virtualcast.jp/blog/2020/01/asmdef_compile_speed/)
- [Unity Assembly Definition 完全に理解した](https://qiita.com/toRisouP/items/d206af3029c7d80326ed)
- [Unity2017.3のAssembly Definition Filesを適切に設定してコンパイルにかかる時間を削減する](https://qiita.com/nkjzm/items/aaad87dc7f7bc9703449)
- [How I Cut Unity Compile Times by 75%](http://www.screaminggoose.com/blog/2019/2/4/how-i-cut-unity-compile-times-by-75)


#### 検証環境

- Windows 10
    - CPU: Core i7-8700K
- **Unity 2018.4.14f**





# TL;DR

先に要点を纏めます。

- **`.csproj`が生成されないほうがコンパイル時間が短くなった**
    - Unity公式のレジストリやリポジトリ[^1]から取得すると`.csproj`が生成されない
    - **コード量を多く含むADFを参照している際に、`.csproj`の有無でコンパイル時間が大幅に変わる事は無い**
        - とは言えども`.csproj`がある場合には0.5~1sec以内でコンパイル時間が伸びているので、チリツモ案件ではあるかもしれない..
    - **ADFの参照が多い上で`.csproj`もある場合には、その分コンパイル時間が伸びる**
        - 入れているパッケージ数が多い場合には注意しても良いかも
        - 公式レジストリから取得した公式パッケージを無闇にローカル参照(`Embedded Package`など)に切り替えるとコンパイル時間が長くなる
            - → 公式パッケージを変更したり追うときにだけ`Embedded Package`に切り替えるなりしたほうが良いかもしれない





# 調査内容

調査にあたっては以下のテストを行ってコンパイル時間を計測してみました。
これらが`.csproj`の有無でコンパイル時間にどう影響を及ぼしていくのかを見ていきます。

- **① 大量のコードを含んだ`ADF`を参照してコンパイル**
    - → コード量が多いアセンブリを参照している時のコンパイル時間の影響について調査
- **② PackageManagerから公式パッケージを30個近くインストールした上でコンパイル**
    - → 数多くのアセンブリを参照している状態でのコンパイル時間の影響について調査


## 計測方法について

コンパイル時間の計測方法としては、以下の2つのスクリプトに変更を掛けてそれぞれのコンパイル時間を計測していきます。
(コードに1行変更を加えてコンパイルを実行)

- ① `Sample.cs`に変更を加えて**`Assembly-CSharp.dll`**と**`Assembly-CSharp-Editor.dll`**のコンパイル時間を計測
- ② `AsmDefSample.cs`に変更を加えて**`AsmDefSample.dll`**及び`Assembly-CSharp.dll`と`Assembly-CSharp-Editor.dll`のコンパイル時間を計測
    - ※`AsmDefSample.asmdef`の**【Auto Referenced】[^3]**は有効化
        - → 故に`Assembly-CSharp.dll`と`Assembly-CSharp-Editor.dll`も合わせてコンパイルされる)

```text:フォルダ構成
[Assets]
    L [Scripts]
        L [AsmDefSample]
            L AsmDefSample.cs
            L AsmDefSample.asmdef
        L Sample.cs

// ※[]はフォルダを指す
```

[^3]: **【Auto Referenced】**・・・`ADF`の設定の一つで、有効にするとデフォルトのアセンブリ(`Assembly-CSharp.dll`/`Assembly-CSharp-Editor.dll`)から参照できるようになる。


※実際のコンパイル時間はコードの変更量なども踏まえて変動していくかと思われるので、あくまでご参考程度に。。


### コンパイル時間の計測

コンパイル時間の計測については以下の記事を参考にしました。

- [Unityでコンパイル時間を可視化する](https://techblog.kayac.com/visualize-compile-time)

コンパイル時間の表記については以下のようにそれぞれのスクリプトを更新した際の時間を載せていきます。[^4]

|Sample.cs|AsmDefSample.cs|
|:--:|:--:|
|-sec|-sec|

[^4]: 記事中で表記しているコンパイル時間は全て「5回実行した際の相加平均(算術平均)」を記載。


#### ※追記

上記の計測では[EditorApplication.isCompiling](https://docs.unity3d.com/ScriptReference/EditorApplication-isCompiling.html)を用いて計測してますが...以下のAPIを使うと**「コンパイル時間」**と**「アセンブリのリロードに掛かった時間」**を分けた上で更に正確な時間を計測できそうです。
(後から知ったので....この記事中では`EditorApplication.isCompiling`の計測結果を記載)

- [CompilationPipeline.assemblyCompilationStarted](https://docs.unity3d.com/ScriptReference/Compilation.CompilationPipeline-assemblyCompilationStarted.html)
- [CompilationPipeline.assemblyCompilationFinished](https://docs.unity3d.com/ScriptReference/Compilation.CompilationPipeline-assemblyCompilationFinished.html)
- [AssemblyReloadEvents.beforeAssemblyReload](https://docs.unity3d.com/ja/2017.4/ScriptReference/AssemblyReloadEvents-beforeAssemblyReload.html)
- [AssemblyReloadEvents.afterAssemblyReload](https://docs.unity3d.com/ja/2017.4/ScriptReference/AssemblyReloadEvents-afterAssemblyReload.html)

更に補足しておくと、`EditorApplication.isCompiling`は2019の最新辺り?から挙動が怪しいっぽいので、こちらも踏まえて上記のAPIを利用したほうが良いかもしれません。

- [Bug EditorApplication.isCompiling is always false](https://forum.unity.com/threads/editorapplication-iscompiling-is-always-false.770126/)



# ① 大量のコードを含んだ`ADF`を参照してコンパイル

**「大量のコードを含んでいる`ADF`を参照していたらどの程度の影響が出るのか？」**について調べてみました。
→ 懸念事項として「大量のコードを含んでいる`ADF`が`.csproj`の有無によってコンパイル時間が大きく変わってくる」とかあると怖いので..。

調査にあたっては以下の**計測用パッケージ**を用意したので、こちらをプロジェクトに導入して計測を行います。

- **[mao-test-h/large-code-sample](https://github.com/mao-test-h/large-code-sample)**
    - **※`LargeCodeSample.asmdef`は【Auto Referenced】を有効化**
    - ※計測対象の`AsmDefSample.asmdef`は`LargeCodeSample.asmdef`を参照するように設定

```text:パッケージのフォルダ構成
[Large code sample]
    L [Runtime]
        L [Generated]
            L ※コンパイル時間計測用の自動生成コード一式
        L LargeCodeSample.asmdef
    L CHANGELOG.md
    L LICENSE.md
    L package.json
    L README.md
```

計測用に用意した大量のコードは自動生成で出力してます。
※自動生成コードとしては「[Unityでコンパイル時間を可視化する](https://techblog.kayac.com/visualize-compile-time)」の`GenerateHuge`を参考に生成。

ちなみに、このコード含むアセンブリ単体のコンパイル時間は**大体`28.35sec`近く掛かりました。**



## 計測結果

条件別に計測結果を載せていきます。

### ● 検証用パッケージをリポジトリ[^1]から直接取得して`ADF`を参照

先ずは[公開リポジトリにある検証用パッケージ](https://github.com/mao-test-h/large-code-sample)を以下のように`manifest.json`に登録して取得します。

冒頭でも軽く触れてある通り、この手法で取得したパッケージは`Library/PackageCache`にキャッシュされて**アセンブリに対応する`.csproj`が生成されない**状態となります。

```json:manifest.json
{
  "dependencies": {
    "com.mao.large-code-sample": "https://github.com/mao-test-h/large-code-sample.git",
    ※中略
```


こちらを計測用コード側(`Sample.cs`/`AsmDefSample.cs`)から参照するようにした上でコンパイルを行います。
計測結果は以下のようになりました。

|Sample.cs|AsmDefSample.cs|
|:--:|:--:|
|4.15sec|4.8sec|

この結果を踏まえつつ、ローカル参照時(`.csproj`が生成されるパターン)と比較してどれくらいコンパイル時間に影響が出るのかを見ていきます。


### **● ローカルに有るパッケージを登録して`ADF`を参照**

↑で公開リポジトリより取得した検証用パッケージをローカル読みに変更する形で検証を進めます。

ローカル読みに切り替える方法としては幾つかありますが、**今回は`Library/PackageCache`以下にある該当パッケージを`Packages`以下に移動することによる`「Embedded Package」`としての切り替えで対応を進めます。**
※詳細はドキュメント参照 → [Local packages inside your Project](https://docs.unity3d.com/Manual/upm-ui-local.html)

このパターンで読み込むと、**アセンブリに対応する`.csproj`が生成されるようになります。**
その上でコンパイル時間の計測結果としては以下のようになりました。

|Sample.cs|AsmDefSample.cs|
|:--:|:--:|
|4.83sec|5.59sec|

**`.csproj`が無い方と比べると少しコンパイル時間が伸びました。** (差分的には1sec以内ではあるが...)
※おそらくは`.csproj`が生成されたことによってコンパイル周りに影響が生じた?と予想してますが...詳細については調べきれておらず...。

何れにせよ「`.csproj`がある場合には、コードサイズに影響してコンパイル時間が大きく伸びる」と言った懸念事項が無いことを確認できました。


### **● パッケージの中身を`Assets/Scripts`以下に直接配置して`ADF`を参照**

そもそもとしてコードを**「Assets以下に置いた時」と「パッケージとして参照する時」とでコンパイル時間に違いが出てくるのか？**が気になったので、参考序に検証用パッケージからコード一式を抜き出す形で簡単に調査してみました。
※この方式でも同じく`.csproj`は生成される。

配置時のフォルダ構成としては以下のようになります。

```text
[Assets]
    L [Scripts]
        L [Runtime]                // 検証用パッケージから抜き出したコード
            L [Generated]
                L ※コンパイル時間計測用の自動生成コード一式
            L LargeCodeSample.asmdef
        L [AsmDefSample]
            L AsmDefSample.cs
            L AsmDefSample.asmdef
        L Sample.cs
```

検証結果としては以下のようになりました。

|Sample.cs|AsmDefSample.cs|
|:--:|:--:|
|4.74sec|4.8sec|

ローカルパッケージ参照と比べると、`Sample.cs`を更新した時のコンパイル時間はほぼ変わらないものの...`AsmDefSample.cs`を更新した時のコンパイル時間が短くなっている...?



## ① まとめ

一先ずはここまでの検証で以下のポイントが見えてきました。

- **コード量を多く含む`ADF`を参照している際に、`.csproj`の有無でコンパイル時間が大幅に変わる事は無い**
    - とは言えども0.5~1sec以内でコンパイル時間が伸びているので、チリツモ案件ではあるかもしれない...

大幅に違いは出ないにせよチリツモ案件かもしれないので、可能そうであれば「公式レジストリやリポジトリからの取得(`.csproj`が生成されない方)」がコンパイル速度的には良いかもしれません。









# ② PackageManagerから公式パッケージを30個近くインストールした上でコンパイル

①の検証だけだと参照しているパッケージそのものは1つしか無いので、情報としては少し乏しい感がありました。
なので**「数多くの`ADF`を参照していたらどの程度の影響が出るのか？」**について調査すべく、PackageManagerから30個近くのパッケージをインストールして検証を行ってみました。

今回導入したパッケージとしては以下のものになります。[^5]

[^5]: Unity2018.4の段階なので一部パッケージは更新されずにPreviewのままなので注意。(e.g. Entitiesとか。2019.3だと最新版が取れる)



<details><summary>**導入パッケージについて (クリックで展開)**</summary><div>

![Art000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/c5f258f3-6194-2327-edb4-db6005db73b5.png)

</div></details>

<details><summary>**`manifest.json` (クリックで展開)**</summary><div>

```json:manifest.json
{
  "dependencies": {
    "com.mao.large-code-sample": "https://github.com/mao-test-h/large-code-sample.git",
    "com.unity.2d.animation": "2.0.0-preview.3",
    "com.unity.2d.ik": "1.0.8-preview.1",
    "com.unity.2d.pixel-perfect": "1.0.1-preview",
    "com.unity.2d.spriteshape": "1.0.14-preview.2",
    "com.unity.addressables": "1.5.0",
    "com.unity.analytics": "3.2.3",
    "com.unity.assetbundlebrowser": "1.7.0",
    "com.unity.assetgraph": "1.6.0-preview",
    "com.unity.build-report-inspector": "0.1.2-preview",
    "com.unity.burst": "1.1.2",
    "com.unity.cinemachine": "2.2.9",
    "com.unity.collab-proxy": "1.2.16",
    "com.unity.collections": "0.0.9-preview.12",
    "com.unity.entities": "0.0.12-preview.24",
    "com.unity.jobs": "0.0.7-preview.6",
    "com.unity.mathematics": "1.1.0",
    "com.unity.package-manager-ui": "2.0.8",
    "com.unity.performance.profile-analyzer": "0.5.0-preview.1",
    "com.unity.postprocessing": "2.2.2",
    "com.unity.purchasing": "2.0.6",
    "com.unity.quicksearch": "1.4.1",
    "com.unity.render-pipelines.core": "4.10.0-preview",
    "com.unity.render-pipelines.lightweight": "4.10.0-preview",
    "com.unity.rendering.hybrid": "0.0.1-preview.4",
    "com.unity.scriptablebuildpipeline": "1.5.4",
    "com.unity.shadergraph": "4.10.0-preview",
    "com.unity.textmeshpro": "1.4.1",
    "com.unity.vectorgraphics": "1.0.0-preview.33",
    "com.unity.visualeffectgraph": "4.10.0-preview",
    "com.unity.modules.ai": "1.0.0",
    "com.unity.modules.animation": "1.0.0",
    "com.unity.modules.assetbundle": "1.0.0",
    "com.unity.modules.audio": "1.0.0",
    "com.unity.modules.cloth": "1.0.0",
    "com.unity.modules.director": "1.0.0",
    "com.unity.modules.imageconversion": "1.0.0",
    "com.unity.modules.imgui": "1.0.0",
    "com.unity.modules.jsonserialize": "1.0.0",
    "com.unity.modules.particlesystem": "1.0.0",
    "com.unity.modules.physics": "1.0.0",
    "com.unity.modules.physics2d": "1.0.0",
    "com.unity.modules.screencapture": "1.0.0",
    "com.unity.modules.terrain": "1.0.0",
    "com.unity.modules.terrainphysics": "1.0.0",
    "com.unity.modules.tilemap": "1.0.0",
    "com.unity.modules.ui": "1.0.0",
    "com.unity.modules.uielements": "1.0.0",
    "com.unity.modules.umbra": "1.0.0",
    "com.unity.modules.unityanalytics": "1.0.0",
    "com.unity.modules.unitywebrequest": "1.0.0",
    "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
    "com.unity.modules.unitywebrequestaudio": "1.0.0",
    "com.unity.modules.unitywebrequesttexture": "1.0.0",
    "com.unity.modules.unitywebrequestwww": "1.0.0",
    "com.unity.modules.vehicles": "1.0.0",
    "com.unity.modules.video": "1.0.0",
    "com.unity.modules.vr": "1.0.0",
    "com.unity.modules.wind": "1.0.0",
    "com.unity.modules.xr": "1.0.0"
  },
  "lock": {
    "com.mao.large-code-sample": {
      "hash": "66eb5cc55d65d8fe0401c5401c4a7501d46f831a",
      "revision": "HEAD"
    }
  }
}
```

</div></details>

<details><summary>**`AsmDefSample.asmdef`の参照設定 (クリックで展開)**</summary><div>

![Art001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/15e44a24-afc9-0e48-8526-46ec6025192d.png)

</div></details>



## 計測結果

条件別に計測結果を載せていきます。

### ● 公式パッケージをPackageManagerからインストールして`ADF`を参照

先ずは**`.csproj`が生成されないパターン**であるインストール取得の方から結果を載せていきます。

|Sample.cs|AsmDefSample.cs|
|:--:|:--:|
|6.81sec|7.41sec|

参照の数が増えているので、コンパイル時間については①の検証結果よりも伸びています。

今回の検証では**全パッケージの参照を持っているので**、実際に使う上では「不必要な`ADF`の参照を切る」なり「【Auto Referenced】の設定を適用する」なりすれば短く出来るかもしれません..。


### **● インストール済みの公式パッケージをローカルに配置して`ADF`を参照**

次に**`.csproj`が生成されるパターン**についてです。
①の検証と同じく、`Library/PackageCache`にキャッシュされているパッケージを`Packages`以下に移動して「Embedded Package」として切り替えます。

計測結果は以下。

|Sample.cs|AsmDefSample.cs|
|:--:|:--:|
|10.45sec|11.19sec|

**割と伸びました。**

上記にもある通り、不必要な`ADF`の依存を切るなり【Auto Referenced】の設定を適用するなりすれば短く出来るかもしれませんが...とりあえずは**「`ADF`の参照が多い上で`.csproj`もある場合には、その分コンパイル時間が伸びる」**点については見えてきました。





# ② まとめ

こちらの検証では以下のポイントが見えてきました。

- **`ADF`の参照が多い上で`.csproj`もある場合には、その分コンパイル時間が伸びる**

余談ですが、よく公式パッケージのソースを追う際には**意図的に「Embedded Package」に切り替えて**運用することがあったのですが...数が多いとコンパイル時間に影響が出てくるかと思われるので、**無闇に切り替えずに必要なときだけ参照する**ようにしたほうが良い感出てきました..。
※意図的に`Embedded Package`に切り替える理由としては、一時的な変更やコード定義ジャンプと言った補完が効くようになるため。


# 未調査項目

この記事中ではまだ調査できていない項目が幾つかあります。
最後に思いついた範囲での項目を幾つかリストアップだけしておきます。

- **「① 大量のコードを含んだADFを参照してコンパイル」 未調査項目**
    - 検証用パッケージの【Auto Referenced】を切ったらコンパイル時間にどう影響が出てくるのか？
- **共通の未調査項目**
    - `Assets`以下にある`ADF`で分割しているアセンブリや、ローカル読み込みのパッケージを`.csproj`非生成状態に出来ないか？
    - Unity2019系統での調査
    - `.csproj`の有無による正確なコンパイルの挙動の把握
        - ※時間では無く、コンパイルステージの挙動的な意味で





# 参考/関連リンク

- [Assembly Definitions](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html)
- [【Unity】「AssemblyDefinitionを分けるとコンパイルが高速になる」ってホント？検証してみた！](https://virtualcast.jp/blog/2020/01/asmdef_compile_speed/)
- [Unity Assembly Definition 完全に理解した](https://qiita.com/toRisouP/items/d206af3029c7d80326ed)
- [Unity2017.3のAssembly Definition Filesを適切に設定してコンパイルにかかる時間を削減する](https://qiita.com/nkjzm/items/aaad87dc7f7bc9703449)
- [How I Cut Unity Compile Times by 75%](http://www.screaminggoose.com/blog/2019/2/4/how-i-cut-unity-compile-times-by-75)
- [Unityでコンパイル時間を可視化する](https://techblog.kayac.com/visualize-compile-time)