C#で クロージャ のパフォーマンスを測定してみる

# 概要
.NET (C#) のパフォーマンス改善に関する記事にて、クロージャを利用した場合 と そうでない場合 における
パフォーマンスの差異を指摘するような情報を拝見しました。
普段、コードを書く際には配慮しないような事項だったため、勉強の意味も込めて実際に測定してみました。

# 情報整理
匿名関数でローカル変数をキャプチャした (取り込んだ) 場合、その関数を クロージャ と呼びます。

C#においてクロージャを作成した場合、コンパイル後に生成されるコード (IL = 中間言語) にて、
クロージャが取り込んだ ローカル変数 を保持するようなクラス構造が、暗黙的に生成されます。

クロージャの外部 (呼び出し側) でローカル変数が変更された場合でも、
クロージャの内部でローカル変数の変更が共有出来るのは、このような仕組みによるものです。

下記ページに非常に判りやすい解説があります。
https://ufcpp.net/study/csharp/sp2_anonymousmethod.html#static

# 環境
- Windows10
- Visual Studio 2019
- .NET Core 2.2
- BenchmarkDotNet

# コードサンプル
サンプルコードを Github にアップしています。
https://github.com/tYoshiyuki/dotnet-core-benchmark-test

尚、今回使用したプログラムサンプルは、下記スライド (41スライド目) を参考にさせていただきました。
https://www.slideshare.net/xin9le/dotnetperformancetips-170268354

# 解説
Linq の FirstOrDefault を例として、ローカル変数をキャプチャした場合 と そうでない場合 とでベンチマークを測定してみます。

ポイントとなるコードは以下の通りです。

```C#:SampleRepository.cs

private readonly List<Sample> _samples = new List<Sample>
{
    new Sample{ Id = 1, Name = "One"},
    new Sample{ Id = 2, Name = "Two"},
    new Sample{ Id = 3, Name = "Three"}
};

public Sample GetByLambda(int id)
{
    // 普通にラムダ式で呼び出し、変数のキャプチャ有り
    return _samples.FirstOrDefault(_ => _.Id == id);
}

public Sample GetByExtension(int id)
{
    // 拡張メソッドを利用、変数のキャプチャ無し
    return _samples.FirstOrDefault((x, state) => x.Id == state, id);
}

public Sample GetByLocalFunction(int id)
{
    // ローカル関数経由で呼び出し、変数のキャプチャ有り
    bool Func(Sample s) => s.Id == id;
    return _samples.FirstOrDefault(Func);
}

```

変数をキャプチャするパターンを2つ、拡張メソッド経由で変数をキャプチャしないパターンを1つ実装しました。
実際のRepositoryでは、DBのようなデータストアからデータを取得しますが、今回はメモリ上より取得するようにしています。


呼び出し側のコードを下記の通りです。

```C#:BenchmarkMain.cs

public BenchmarkMain()
{
    _sampleRepository = new SampleRepository();
    _indexes = Enumerable.Range(0, 10000).ToArray();
}

[Benchmark]
public void CaseLambda()
{
    // 普通にラムダ式で呼び出し、変数のキャプチャ有り
    foreach (var i in _indexes)
    {
        var id = i % 3 + 1;
        var sample = _sampleRepository.GetByLambda(id);
        if (sample == null) Console.WriteLine("No data");
    }
}

・・・以下略

```

それぞれのパターンで　10,000回 の呼び出しを行っています。
ベンチマークの測定には BenchmarkDotNet を利用しました。

以下、測定結果を掲載します。

|            Method |       Mean |    Error |    StdDev |     Median |
|------------------ |-----------:|---------:|----------:|-----------:|
|        CaseLambda | 1,234.8 us | 35.82 us | 101.04 us | 1,198.6 us |
|     CaseExtension |   782.1 us | 23.95 us |  69.86 us |   771.0 us |
| CaseLocalFunction | 1,401.2 us | 38.64 us | 113.32 us | 1,371.5 us |

やはり、ローカル変数をキャプチャしない方が パフォーマンス的に有利 であるようです。
実践では、コードの可読性やパフォーマンスをどこまで配慮すべきかという観点もあるとは思いますが、
実装上のポイントとして、認識しておいた方が良いかなと感じました。

# おまけ
暗黙的に生成されたクラスを IL で確認してみました。
素人には読めるような内容ではないのですが、キャプチャされたローカル変数を保持するクラス構造が
生成されているという点は確認出来ます。

```
  .class nested private sealed auto ansi beforefieldinit
    '<>c__DisplayClass1_0'
      extends [System.Runtime]System.Object
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
      = (01 00 00 00 )

    .field public int32 id　// ★★★ この部分 ★★★

    .method public hidebysig specialname rtspecialname instance void
      .ctor() cil managed
    {
      .maxstack 8

      IL_0000: ldarg.0      // this
      IL_0001: call         instance void [System.Runtime]System.Object::.ctor()
      IL_0006: nop
      IL_0007: ret

    } // end of method '<>c__DisplayClass1_0'::.ctor

    .method assembly hidebysig instance bool
      '<GetByLambda>b__0'(
        class DotNetCoreBenchmarkTest.Repository.Sample _
      ) cil managed
    {
      .maxstack 8

      // [19 49 - 19 59]
      IL_0000: ldarg.1      // _
      IL_0001: callvirt     instance int32 DotNetCoreBenchmarkTest.Repository.Sample::get_Id()
      IL_0006: ldarg.0      // this
      IL_0007: ldfld        int32 DotNetCoreBenchmarkTest.Repository.SampleRepository/'<>c__DisplayClass1_0'::id
      IL_000c: ceq
      IL_000e: ret

    } // end of method '<>c__DisplayClass1_0'::'<GetByLambda>b__0'
  } // end of class '<>c__DisplayClass1_0'
```