6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[C#] リフレクションでなるべく高速にクラスメンバを取得

Last updated at Posted at 2024-10-05

リフレクションの高速化は、既に先人が研究し尽くしてる感のあるテクニックですが、計測結果などがまとまったものが見つからなかったので、改めて自分で速度を測ってみました。

テスト用コード
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Linq.Expressions;
using System.Reflection;

public class ValueClass
{
    public int ValueProperty { get; set; } = 10;
    public int ValueField = 20;
}

public class Accessor
{
    public static Func<TClass, TValue> CreateGetterDelegate<TClass, TValue>(string propertyName)
        => (Func<TClass, TValue>)Delegate.CreateDelegate(
            typeof(Func<TClass, TValue>),
            typeof(TClass).GetProperty(propertyName).GetMethod
            );

    public static Func<TClass, TValue> CreateGetterExpression<TClass, TValue>(string name)
    {
        var instance = Expression.Parameter(typeof(TClass));
        var member = Expression.PropertyOrField(instance, name);
        return (Func<TClass, TValue>)Expression.Lambda(member, instance).Compile();
    }

    public static Func<object, object> CreateGetterExpression(Type typeClass, string name)
    {
        var oinstance = Expression.Parameter(typeof(object));
        var instance = Expression.Convert(oinstance, typeClass);
        var member = Expression.PropertyOrField(instance, name);
        var omember = Expression.Convert(member, typeof(object));
        return (Func<object, object>)Expression.Lambda(omember, oinstance).Compile();
    }
}

[ShortRunJob]
[MemoryDiagnoser(false)]
public class ReflectionBench
{
    private static PropertyInfo _CacheGetPropertyInfo
        = typeof(ValueClass).GetProperty(nameof(ValueClass.ValueProperty));

    private static Func<ValueClass, int> _CacheGetPropertyDelegate
        = Accessor.CreateGetterDelegate<ValueClass, int>(nameof(ValueClass.ValueProperty));

    private static Func<ValueClass, int> _CachePropertyExpression
        = Accessor.CreateGetterExpression<ValueClass, int>(nameof(ValueClass.ValueProperty));

    private static Func<object, object> _CacheGetPropertyExpressionObj
        = Accessor.CreateGetterExpression(typeof(ValueClass), nameof(ValueClass.ValueProperty));

    private static FieldInfo _CacheGetFieldInfo
        = typeof(ValueClass).GetField(nameof(ValueClass.ValueField));

    private static Func<ValueClass, int> _CacheGetFieldExpression
        = Accessor.CreateGetterExpression<ValueClass, int>(nameof(ValueClass.ValueField));

    private static Func<object, object> _CacheGetFieldExpressionObj
        = Accessor.CreateGetterExpression(typeof(ValueClass), nameof(ValueClass.ValueField));

    private static ValueClass _ValueClass = new ValueClass();

    // 直接プロパティ取得
    [Benchmark]
    public int GetProperty()
        => _ValueClass.ValueProperty;

    // PropertyInfoでプロパティ取得
    [Benchmark]
    public int GetPropertyReflection()
        => (int)_CacheGetPropertyInfo.GetValue(_ValueClass);

    // PropertyInfoからデリゲートを作成してプロパティ取得
    [Benchmark]
    public int GetPropertyDelegate()
        => _CacheGetPropertyDelegate(_ValueClass);

    // Expressionでプロパティ取得
    [Benchmark]
    public int GetPropertyExpression()
        => _CachePropertyExpression(_ValueClass);

    // Expressionでプロパティ取得(object型経由)
    [Benchmark]
    public int GetPropertyExpressionObj()
        => (int)_CacheGetPropertyExpressionObj(_ValueClass);

    // 直接フィールド取得
    [Benchmark]
    public int GetField()
        => _ValueClass.ValueField;

    // FieldInfoでフィールド取得
    [Benchmark]
    public int GetFieldReflection()
        => (int)_CacheGetFieldInfo.GetValue(_ValueClass);

    // Expressionでフィールド取得
    [Benchmark]
    public int GetFieldExpression()
        => _CacheGetFieldExpression(_ValueClass);

    // Expressionでフィールド取得(object型経由)
    [Benchmark]
    public int GetFieldExpressionObj()
        => (int)_CacheGetFieldExpressionObj(_ValueClass);
}

public class Program
{
    public static void Main()
    {
#if DEBUG
        var cls = new ReflectionBench();
        Console.WriteLine(cls.GetProperty());
        Console.WriteLine(cls.GetPropertyReflection());
        Console.WriteLine(cls.GetPropertyDelegate());
        Console.WriteLine(cls.GetPropertyExpression());
        Console.WriteLine(cls.GetPropertyExpressionObj());
        Console.WriteLine(cls.GetField());
        Console.WriteLine(cls.GetFieldReflection());
        Console.WriteLine(cls.GetFieldExpression());
        Console.WriteLine(cls.GetFieldExpressionObj());
        Console.ReadLine();
#else
        BenchmarkRunner.Run<ReflectionBench>();
#endif
    }
}

.NET Framework 4.8.1 (x64)

Method Mean Error StdDev Allocated
GetProperty 0.0352 ns 0.2141 ns 0.0117 ns -
GetPropertyReflection 88.2162 ns 38.6461 ns 2.1183 ns 24 B
GetPropertyDelegate 2.2311 ns 0.0674 ns 0.0037 ns -
GetPropertyExpression 4.6539 ns 1.5632 ns 0.0857 ns -
GetPropertyExpressionObj 7.3046 ns 2.5343 ns 0.1389 ns 24 B
GetField 0.0338 ns 0.1297 ns 0.0071 ns -
GetFieldReflection 45.1978 ns 34.1884 ns 1.8740 ns 24 B
GetFieldExpression 0.2263 ns 0.0259 ns 0.0014 ns -
GetFieldExpressionObj 3.0927 ns 0.9122 ns 0.0500 ns 24 B

.NET Frameworkでは、プロパティの取得はExpressionよりPropertyInfoから作成したデリゲートの方が速いようです。フィールドの取得は、FieldInfoからはCreateDelegate出来ないので、Expressionはかなり効果があります。

.NET 8 (x64)

Method Mean Error StdDev Allocated
GetProperty 0.2103 ns 0.1799 ns 0.0099 ns -
GetPropertyReflection 9.7578 ns 2.1925 ns 0.1202 ns 24 B
GetPropertyDelegate 0.6400 ns 0.0072 ns 0.0004 ns -
GetPropertyExpression 0.2227 ns 0.1074 ns 0.0059 ns -
GetPropertyExpressionObj 3.8620 ns 1.1930 ns 0.0654 ns 24 B
GetField 0.2165 ns 0.0452 ns 0.0025 ns -
GetFieldReflection 28.9341 ns 4.3855 ns 0.2404 ns 24 B
GetFieldExpression 0.2135 ns 0.1056 ns 0.0058 ns -
GetFieldExpressionObj 3.7763 ns 0.1409 ns 0.0077 ns 24 B

.NET8は、Expressionでの取得がプロパティ・フィールド共に直接アクセスと遜色ないレベルで速いです。また、普通にリフレクションした場合の速度も全体的に改善されているようです。(とはいえ、相変わらず直接アクセスと比較するとかなり遅いですが)

参考記事

リフレクションは遅いから情報をキャッシュするとかのお話
Expression Treeのこね方・入門編 - 動的にデリゲートを生成してリフレクションを高速化
Faster than Reflection: Delegates - Part 1

6
8
0

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
6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?