LoginSignup
24
14

C# のリフレクションの性能を図る。前から気になっていることを調べてみた

Last updated at Posted at 2023-11-09

はじめに

  • 個人的にはやめろとか思うのですが、よくバックエンドのロジックで、こんな風なのを毎回実行しているコードを見かけます。
model.GetType().GetProperty("Name")?.SetValue(model, "aaa");

以下のことを思います。

  • GetProperty は毎回取得する必要がない、
  • リフレクションだから遅い。
  • 素直にName="aaa" として静的コード実行したほうが早い。
  • せめて式木でキャッシュしてください。
  • 1個WebApiとかで、1個の要求として考えたらたらたいしたことないけどサーバー全体して考えて、かつ共通である程度のロジックから呼び出せれているなら早く終わるコードの方がよい。

.NET5 とか.NET 6とかでリフレクション速度改善されているなんて声も聴きベンチマークをしてみました。
※2023/11/12 追記各Runtimeごとの性能を図ってみる。

ベンチマークコード

ベンチマーク.NET を使い以下のようなコードを書きました。

[SimpleJob(RuntimeMoniker.Net481)]
[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[SimpleJob(RuntimeMoniker.Net50)]
[SimpleJob(RuntimeMoniker.Net60)]
[SimpleJob(RuntimeMoniker.Net70)]
[SimpleJob(RuntimeMoniker.Net80)]
[MemoryDiagnoser]
[AsciiDocExporter]
[MarkdownExporterAttribute.Default]
[HtmlExporter]
public class ReflectionTest
{
    private static readonly PropertyInfo propertyInfoId;
    private static readonly PropertyInfo propertyInfoName;
    private static readonly Action<object, object> treeId;
    private static readonly Action<object, object> treeName;

    static ReflectionTest()
    {
        propertyInfoId = typeof(TestModel).GetProperty("Id")!;
        propertyInfoName = typeof(TestModel).GetProperty("Name")!;
        treeId = SetDelegate(typeof(TestModel), "Id");
        treeName = SetDelegate(typeof(TestModel), "Name");
    }
    static Action<object, object> SetDelegate(Type type, string memberName)
    {
        var target = Expression.Parameter(typeof(object), "target");
        var value = Expression.Parameter(typeof(object), "value");

        var left =
            Expression.PropertyOrField(
                Expression.Convert(target, type), memberName);

        var right = Expression.Convert(value, left.Type);

        var lambda = Expression.Lambda<Action<object, object>>(
            Expression.Assign(left, right),
            target, value);

        return lambda.Compile();
    }


    [Benchmark]
    public void NonReflection()
    {
        var model = new TestModel();
        model.Id = 1;
        model.Name = "aaa";
    }

    [Benchmark]
    public void Reflection()
    {
        var model = new TestModel();
        model.GetType().GetProperty("Id")?.SetValue(model, 1);
        model.GetType().GetProperty("Name")?.SetValue(model, "aaa");
    }
    [Benchmark]
    public void CacheReflection()
    {
        var model = new TestModel();
        propertyInfoId.SetValue(model, 1);
        propertyInfoName.SetValue(model, "aaa");
    }
    [Benchmark]
    public void ExpressionTree()
    {
        var model = new TestModel();
        treeId(model, 1);
        treeName(model, "aaa");

    }
}
  • projectファイルに以下を追加
 <TargetFrameworks>net7.0;net6.0;net48;netcoreapp3.1;net5.0;net8.0</TargetFrameworks>

実行結果

BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.2428/23H2/2023Update/SunValley3)
13th Gen Intel Core i7-13700H, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
.NET 5.0 : .NET 5.0.17 (5.0.1722.21314), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.24 (6.0.2423.51814), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
.NET Core 3.1 : .NET Core 3.1.32 (CoreCLR 4.700.22.55902, CoreFX 4.700.22.56512), X64 RyuJIT AVX2
.NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9181.0), X64 RyuJIT VectorSize=256

Method Runtime Mean Error StdDev Ratio Gen0 Allocated
NonReflection .NET 5.0 3.834 ns 0.0369 ns 0.0345 ns 1.00 0.0025 32 B
Reflection .NET 5.0 176.898 ns 0.8622 ns 0.8065 ns 46.14 0.0145 184 B
CacheReflection .NET 5.0 129.256 ns 1.0773 ns 1.0077 ns 33.71 0.0145 184 B
ExpressionTree .NET 5.0 6.791 ns 0.0521 ns 0.0462 ns 1.77 0.0045 56 B
NonReflection .NET 6.0 4.052 ns 0.0566 ns 0.0502 ns 1.00 0.0025 32 B
Reflection .NET 6.0 116.984 ns 1.1387 ns 0.8890 ns 28.78 0.0043 56 B
CacheReflection .NET 6.0 88.294 ns 0.3755 ns 0.3513 ns 21.79 0.0044 56 B
ExpressionTree .NET 6.0 7.408 ns 0.0394 ns 0.0368 ns 1.83 0.0045 56 B
NonReflection .NET 7.0 3.262 ns 0.0866 ns 0.1669 ns 1.00 0.0025 32 B
Reflection .NET 7.0 64.454 ns 1.2831 ns 1.8401 ns 19.84 0.0044 56 B
CacheReflection .NET 7.0 35.141 ns 0.7297 ns 1.8308 ns 10.81 0.0044 56 B
ExpressionTree .NET 7.0 6.885 ns 0.1626 ns 0.3637 ns 2.11 0.0045 56 B
NonReflection .NET 8.0 2.135 ns 0.0608 ns 0.0624 ns 1.00 0.0025 32 B
Reflection .NET 8.0 43.243 ns 0.2352 ns 0.2085 ns 20.21 0.0044 56 B
CacheReflection .NET 8.0 21.091 ns 0.1027 ns 0.0910 ns 9.86 0.0044 56 B
ExpressionTree .NET 8.0 5.552 ns 0.0300 ns 0.0266 ns 2.59 0.0045 56 B
NonReflection .NET Core 3.1 3.597 ns 0.0442 ns 0.0413 ns 1.00 0.0025 32 B
Reflection .NET Core 3.1 193.061 ns 0.9542 ns 0.8925 ns 53.68 0.0145 184 B
CacheReflection .NET Core 3.1 148.658 ns 0.8488 ns 0.6627 ns 41.32 0.0145 184 B
ExpressionTree .NET Core 3.1 7.168 ns 0.1127 ns 0.1652 ns 2.01 0.0045 56 B
NonReflection .NET Framework 4.8.1 3.574 ns 0.0365 ns 0.0342 ns 1.00 0.0051 32 B
Reflection .NET Framework 4.8.1 264.374 ns 0.9149 ns 0.8110 ns 74.04 0.0291 185 B
CacheReflection .NET Framework 4.8.1 192.431 ns 0.8518 ns 0.6651 ns 53.93 0.0293 185 B
ExpressionTree .NET Framework 4.8.1 12.734 ns 0.0410 ns 0.0384 ns 3.56 0.0089 56 B
  • .NET 7.0 なった時にだいぶリフレクションの性能が改善されているのがわかった。個人的なイメージはxxx ns→x ns くらいの印象だったので、とはいえ、19倍とか20倍の性能差ならリフレクション使わない方がいいかなと、propertyinfo キャッシュするだけでも、それなりには改善されることはわかった。.NETのバージョンが上がるたびに早くはなっていっている。
  • ただ今の時代リフレクションを使わずに問題のようなコードはソースジェネレーターで静的生成する方がよいなと思いました。Entityにある共通的な項目とかを設定するだけなら。
24
14
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
14