Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET reflection 3 times slower in .NET 4.7.x than in 3.5

I have an application that we upgraded form .NET 3.5 to .NET 4.7.2. The only problem really is performance from part of our code that uses reflection. Entire situation is best explained in a simple sample I uploaded to Gist: https://gist.github.com/vkocjancic/3e8a6b3496c412a75b1c85a1d2ba1111

Basically, we have a POCO class, which property methods throw exceptions, if value is not set for properties of non-nullable types.

[EDIT]:

  1. Yes, I know that is not correct or a good pattern to use, but, the application started in .NET 1.1.

  2. Yes, it should have been fixed long ago. It wasn't.

A reflection is then used to get property names and values of instances of POCO class and popluate it to DataTable.

The sample and real life project source code is exactly the same. The only difference is that in one case it is compiled using .NET 3.5 and in second using .NET 4.7.2.

This is the average time elapsed in milliseconds for 10 invocations:

.NET 3.5      ->  231.1 ms
.NET 4.7.2    ->  713.5 ms
.NET Core 2.2 -> 1013.2 ms

Can anyone elaborate why reflection is about 3 times slower in .NET 4.7.2 than in .NET 3.5 and how to fix that. The lag only happens, when properties are not set and throw exceptions. If you populate property values, there is no difference in performance.

If I run sample in debugger, .NET 3.5 build never triggers MissingFieldException. Build using .NET 4.7.2 triggers every exception.

[EDIT]

As @bevan mentioned, the slowdown actually occurs when switching to .NET 4.0. > Also the problem boils down to exception being thrown and handled 3 times faster in .NET 3.5 than it is in .NET 4.0

like image 250
Vladimir Kocjancic Avatar asked Jan 21 '20 21:01

Vladimir Kocjancic


1 Answers

As i mentioned in the comments, if you call the IsPROP_NUMERIC_ANull function before you access the properties, it wont throw any more exceptions and is super fast.

foreach (var myObject in objects)
{
    var row = table.NewRow();

    // TODO implement some caching: dont call GetProperties(), GetMethod(), for every object
    var type = myObject.GetType();
    var properties = type.GetProperties();

    foreach (var info in properties)
    {
        try
        {
            // call the IsNullMethod for the property, eg "IsPROP_NUMERIC_ANull"
            var isNullMethodName = $"Is{info.Name}Null";
            var isNullMethod = type.GetMethod(isNullMethodName, BindingFlags.Instance | BindingFlags.Public);

            if (isNullMethod != null)
            {
                var fldIsNull = (bool)isNullMethod.Invoke(myObject, new object[] { });
                if (!fldIsNull)
                    row[info.Name] = info.GetValue(myObject, null);
            } else
            {
                // isNullMethod doesn't exist
                row[info.Name] = info.GetValue(myObject, null);
            }
        }
        catch
        {
            // do nothing
        }
    }
    table.Rows.Add(row);
}

Total runtime is 3 ms vs 19500 ms you had before.

If you can redesign this i would use Cached Delegates like Marc Gravell proposed.

like image 149
Charles Avatar answered Sep 28 '22 02:09

Charles