UnityでGC Allocを発生させないC#コーディング

Unityでコードを書いたときにGC Allocが発生するパターンを調べた。GC Allocが少なくなるパターンをgoodとしたが、コードがわかりづらくなる場合もあるので必ずしも書き換えをする必要はない。

Unity 2018.4 で動作確認

### 文字と数値の連結

```c#:bad
int i = 123;
string s = "num_" + i;
```

```c#:good
int i = 123;
string s = "num_" + i.ToString();
```

string.Concatにobjectとして渡されてボックス化されてしまうので文字列にする。

### 4つまでの文字列連結

```c#:bad
string[] num = { "0", "1", "2", "3", "4", "5" };
// 96byte
s = num[0];
s += num[1];
s += num[2];
s += num[3];
```

```c#:good
string[] num = { "0", "1", "2", "3", "4", "5" };
// 34byte
string s = num[0] + num[1] + num[2] + num[3];
```

連結するごとに新しい文字列が生成されるが、4つまでの連結ならConcat(string, string, string, string)が呼ばれて一度に連結できる。

### ループ中の文字列連結

```c#:good
var sb = new System.Text.StringBuilder();
for (int i = 0; i < num.Length; i++)
{
    sb.Append(num[i]);
}
string s = sb.ToString();
```

ループでたくさん文字列連結することがある場合は、一回ごとに文字列が生成されないようにStringBuilderを使う。

### 文字列の配列の連結

```c#:good
string[] num = { "0", "1", "2", "3", "4", "5" };
// 38byte
string s = string.Join("", num);
```

文字列の配列を連結する場合はJoinを使う。

### string.Format

```c#:bad
string[] num = new string[10];
for (int i = 0; i < num.Length; i++)
{
    // 84byte
    num[i] = string.Format("num_{0}", i);
}
```

```c#:good
string[] num = new string[10];
for (int i = 0; i < num.Length; i++)
{
    // 64byte
    num[i] = $"num_{i.ToString()}";
}
```
string.Formatよりも文字列補間($"")を使う。文字列補間のほうが見やすく、引数が文字列だけの場合は単純な文字列連結になりstring.Formatが呼び出されない。

### StringBuilderへの数値の追加

```c#
var sb = new System.Text.StringBuilder();
int n = 100;
sb.Append(n);
```

.NET Frameworkでは整数はToStringされて追加される。ToStringしたくない場合、1文字ずつ追加する必要がある。
https://gist.github.com/sapphire-al2o3/ba7d6a80836a2e5ee117abb4c3d75132

### Enumのキャスト

```c#:bad
// 40byte
EnumType e = (EnumType)System.Enum.ToObject(typeof(EnumType), i);
// 40byte
i = System.Convert.ToInt32(e);
```

```c#:good
// 0byte
EnumType e = (EnumType)i;
// 0byte
i = (int)e;
```

### EnumをDictionaryのKeyにしない(.NET 3.5)

```c#:bad
var d = new Dictionary<EnumType, int>();
```

```c#:good
var d = new Dictionary<int, int>();
```

.NET 4ならボックス化しないのでEnumをKeyにしてアクセスしてもGC Allocは発生しないが、低速なのでintをKeyにしたほうがいい。

### コルーチンの戻り値

```c#:bad
// 20byte
yield return 0;
```

```c#:good
// 0byte
yield return null;
```

0を戻り値にした場合、ボックス化してGC Allocが発生する。

### コルーチンでのforeach

```c#:bad
IEnumrator Sum(int[] array)
{
    int s = 0;
    foreach (var a in array)
    {
        s += a;
        yield return null;
    }
}

int[] array = { 1, 2, 3, 4, 5 };
StartCoroutine(Sum());
```

```c#:good
IEnumrator Sum(int[] array)
{
    int s = 0;
    for (int i = 0; i < array.Length; i++)
    {
        s += a[i];
        yield return null;
    }
}

int[] array = { 1, 2, 3, 4, 5 };
StartCoroutine(Sum());
```

foreachよりもforのほうが一時変数が少ないのでコルーチンのサイズが小さくなる。

### Listのサイズ指定をする

```c#:bad
List<int> list = new List<int>();
// 8.3Kbyte
for (int i = 0; i < 1000; i++)
{
    list.Add(i);
}
```

```c#:good
List<int> list = new List<int>(1000);
// 4.0Kbyte
for (int i = 0; i < 1000; i++)
{
    list.Add(i);
}
```

ListやDictionaryはあらかじめ追加するサイズが分かっている場合、生成するときにCapacityを指定する。

### 配列のソート

```c#:bad
int[] array = new int[100];
// 10.9Kbyte
for (int i = 0; i < 100; i++)
{
    Array.Sort(array);
}
```

```c#:good
int[] array = new int[100];
// 112byte
for (int i = 0; i < 100; i++)
{
    Array.Sort(array, (x, y) => x - y);
}
```

たとえintの配列であってもラムダ式を指定しないと不要なキャストが発生してしまう。(.NET Coreだと大丈夫そう)

### Delegate

```c#:bad
void Log(int value) { Debug.Log(value); }

void Squared(int value, Action<void> callback)
{
    int v = value * 2;
    callback(v);
}

int[] num = { 0, 1, 2, 3, 4 };
foreach (var n in num)
{
    Squared(n, Log);
}
```

```c#:good
void Log(int value) { Debug.Log(value); }

void Squared(int value, Action<void> callback)
{
    int v = value * 2;
    callback(v);
}

int[] num = { 0, 1, 2, 3, 4 };
var log = Log;
foreach (var n in num)
{
    Squared(n, log);
}
```

何度も呼び出す関数はキャッシュする。

### ラムダ式のスコープ

```c#:bad
List<int> RemoveList(List<int> list, int v)
{
    if (list != null && list.Count > 0)
    {
        return list.FindAll(x => x != v);
    }
    return null;
}
```

```c#:good
List<int> RemoveList(List<int> list, int v)
{
    if (list != null && list.Count > 0)
    {
        int t = v;
        return list.FindAll(x => x != t);
    }
    return null;
}
```
listがnullの場合は何もしないがbadのほうは関数を呼び出しただけでGC Allocが発生する。
関数に入ったときに引数をキャプチャしたラムダ式が生成されてしまうので引数を内側のスコープにキャッシュしてキャプチャするとスコープに入らない場合はラムダ式が生成されない。

### IListのループ

```c#:bad
IList list = new string[10];
foreach (var e in list) {}
```

```c#:good
IList list = new string[10];
for (int i = 0; i < list.Count; i++) {}
```

インターフェース経由でforeachを使うとボックス化が発生するのでforを使う。

### メンバーの順番

```c#:bad
// 80byte
class C
{
    byte a;
    long b;
    byte c;
    long d;
    byte e;
    long f;
    byte g;
    long h;
}
```

```c#:good
// 56byte
class C
{
    long b;
    long d;
    long f;
    long h;
    byte a;
    byte c;
    byte e;
    byte g;
}
```

Unityではclassでも自動で適切なメモリレイアウトにならないようなので無駄なパディングが入らないようにメンバー定義の順番を考慮する。

### エンコーディング

```c#:bad
string s = "hoge";
int n = System.Text.Encoding.GetEncoding("UTF-8").GetByteCount(s);
```

```c#:good
string s = "hoge";
int n = System.Text.Encoding.UTF8.GetByteCount(s);
```

GetEncodingを呼び出すと毎回クラスが生成されるのでキャッシュされるUTF8を使う。

## UnityのAPI

### Object.name

```c#:bad
for (int i = 0; i < 4; i++)
{
    Debug.Log(go.name);
}
```

`name` プロパティにアクセスするたびにGC Allocが発生するのでキャッシュする。

### Application.***Path

- Application.persistentDataPath
- Application.temporaryCachePath

などもアクセスするたびに文字列が生成されてしまうのでキャッシュする。

### Renderer.materials

```c#:bad
for (int i = 0; i < renderer.materials.length; i++)
{
}
```

```c#:good
foreach (var m in renderer.materials)
{
}
```

`Renderer.materials` はプロパティで配列を生成して返しているのでループで毎回アクセスしないようにする。

### Input.touches

```c#:bad
// 80byte
foreach (var touch in Input.touches)
{
}
```

```c#:good
// 0byte
for (int i = 0; i < Input.touchCount; i++)
{
    var touch = Input.GetTouch(i);
}
```

こちらも配列を生成して返すので `Input.GetTouch` を使う。

### Animator.GetParameter

```c#:bad
for (int i = 0; i < animator.parameterCount; i++)
{
    // animators.parameters[i].nameとほぼ同じ
    Debug.Log(animator.GetParameter(i).name);
}
```

```c#:good
foreach (var paramter in animator.parameters)
{
    Debug.Log(paremter.name);
}
```

`Animator.parameters` は配列を返すAPIなのでキャッシュする。
`Animator.GetParameter` は `Animator.parameters` を内部で呼び出しているだけなので使わない。

### 結果を配列で返すAPI

```c#:good
void GetComponentsInChildren (List<T> results)
void GetComponentsInChildren (List<T> results)
void GetComponentsInParent (bool includeInactive, List<T> results)
int Physics.RaycastNonAlloc (... , RaycastHit[] results, ...)
Animator.GetCurrentAnimatorClipInfo (int layerIndex, List<AnimatorClipInfo> clips)
```

何度も呼び出す場合は、引数に配列やリストを渡して結果を受け取る関数を使う。

<!--

## 使わないほうがいい機能

- 正規表現
- リフレクション

これらの機能はゲームのようなリアルタイム処理を想定されていないので毎フレーム処理されるような箇所では極力使わない。

-->