Skip to content

Improve performance of ReflectHelper.GetMethod/Definition #2352

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 21, 2020

Conversation

hazzik
Copy link
Member

@hazzik hazzik commented Apr 17, 2020

Usage of ReflectHelper.GetMethod requires a runtime construction of a lambda expression and later deconstruction of it.

The decompiled code of old GetMethod
    .method public hidebysig 
        instance void M2 () cil managed 
    {
        // Method begins at RVA 0x2080
        // Code size 84 (0x54)
        .maxstack 7

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldtoken C
        IL_0007: call class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Type::GetTypeFromHandle(valuetype [System.Private.CoreLib]System.RuntimeTypeHandle)
        IL_000c: call class [System.Linq.Expressions]System.Linq.Expressions.ConstantExpression [System.Linq.Expressions]System.Linq.Expressions.Expression::Constant(object, class [System.Private.CoreLib]System.Type)
        IL_0011: ldtoken method instance int32 C::A(int32)
        IL_0016: call class [System.Private.CoreLib]System.Reflection.MethodBase [System.Private.CoreLib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [System.Private.CoreLib]System.RuntimeMethodHandle)
        IL_001b: castclass [System.Private.CoreLib]System.Reflection.MethodInfo
        IL_0020: ldc.i4.1
        IL_0021: newarr [System.Linq.Expressions]System.Linq.Expressions.Expression
        IL_0026: dup
        IL_0027: ldc.i4.0
        IL_0028: ldc.i4.0
        IL_0029: box [System.Private.CoreLib]System.Int32
        IL_002e: ldtoken [System.Private.CoreLib]System.Int32
        IL_0033: call class [System.Private.CoreLib]System.Type [System.Private.CoreLib]System.Type::GetTypeFromHandle(valuetype [System.Private.CoreLib]System.RuntimeTypeHandle)
        IL_0038: call class [System.Linq.Expressions]System.Linq.Expressions.ConstantExpression [System.Linq.Expressions]System.Linq.Expressions.Expression::Constant(object, class [System.Private.CoreLib]System.Type)
        IL_003d: stelem.ref
        IL_003e: call class [System.Linq.Expressions]System.Linq.Expressions.MethodCallExpression [System.Linq.Expressions]System.Linq.Expressions.Expression::Call(class [System.Linq.Expressions]System.Linq.Expressions.Expression, class [System.Private.CoreLib]System.Reflection.MethodInfo, class [System.Linq.Expressions]System.Linq.Expressions.Expression[])
        IL_0043: call !!0[] [System.Private.CoreLib]System.Array::Empty<class [System.Linq.Expressions]System.Linq.Expressions.ParameterExpression>()
        IL_0048: call class [System.Linq.Expressions]System.Linq.Expressions.Expression`1<!!0> [System.Linq.Expressions]System.Linq.Expressions.Expression::Lambda<class [System.Private.CoreLib]System.Action>(class [System.Linq.Expressions]System.Linq.Expressions.Expression, class [System.Linq.Expressions]System.Linq.Expressions.ParameterExpression[])
        IL_004d: call class [System.Private.CoreLib]System.Reflection.MethodInfo C::GetMethod(class [System.Linq.Expressions]System.Linq.Expressions.Expression`1<class [System.Private.CoreLib]System.Action>)
        IL_0052: pop
        IL_0053: ret
    } // end of method C::M2

    .method public hidebysig static 
        class [System.Private.CoreLib]System.Reflection.MethodInfo GetMethod (
            class [System.Linq.Expressions]System.Linq.Expressions.Expression`1<class [System.Private.CoreLib]System.Action> 'method'
        ) cil managed 
    {
        // Method begins at RVA 0x20e0
        // Code size 41 (0x29)
        .maxstack 2
        .locals init (
            [0] bool,
            [1] class [System.Private.CoreLib]System.Reflection.MethodInfo
        )

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldnull
        IL_0003: ceq
        IL_0005: stloc.0
        // sequence point: hidden
        IL_0006: ldloc.0
        IL_0007: brfalse.s IL_0014

        IL_0009: ldstr "method"
        IL_000e: newobj instance void [System.Private.CoreLib]System.ArgumentNullException::.ctor(string)
        IL_0013: throw

        IL_0014: ldarg.0
        IL_0015: callvirt instance class [System.Linq.Expressions]System.Linq.Expressions.Expression [System.Linq.Expressions]System.Linq.Expressions.LambdaExpression::get_Body()
        IL_001a: castclass [System.Linq.Expressions]System.Linq.Expressions.MethodCallExpression
        IL_001f: callvirt instance class [System.Private.CoreLib]System.Reflection.MethodInfo [System.Linq.Expressions]System.Linq.Expressions.MethodCallExpression::get_Method()
        IL_0024: stloc.1
        IL_0025: br.s IL_0027

        IL_0027: ldloc.1
        IL_0028: ret
    } // end of method C::GetMethod

The idea of a new method is that we utilize the creation of the delegate from a method group and later use delegate's .Method property.

The decompiled code of FastGetMethod
    // Methods
    .method public hidebysig 
        instance void M1 () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 21 (0x15)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldftn instance int32 C::A(int32)
        IL_0008: newobj instance void class [System.Private.CoreLib]System.Func`2<int32, int32>::.ctor(object, native int)
        IL_000d: ldc.i4.0
        IL_000e: call class [System.Private.CoreLib]System.Reflection.MethodInfo C::FastGetMethod<int32, int32>(class [System.Private.CoreLib]System.Func`2<!!0, !!1>, !!0)
        IL_0013: pop
        IL_0014: ret
    } // end of method C::M1

    .method public hidebysig static 
        class [System.Private.CoreLib]System.Reflection.MethodInfo FastGetMethod<T, TResult> (
            class [System.Private.CoreLib]System.Func`2<!!T, !!TResult> func,
            !!T a
        ) cil managed 
    {
        // Method begins at RVA 0x2068
        // Code size 12 (0xc)
        .maxstack 1
        .locals init (
            [0] class [System.Private.CoreLib]System.Reflection.MethodInfo
        )

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: callvirt instance class [System.Private.CoreLib]System.Reflection.MethodInfo [System.Private.CoreLib]System.Delegate::get_Method()
        IL_0007: stloc.0
        IL_0008: br.s IL_000a

        IL_000a: ldloc.0
        IL_000b: ret
    } // end of method C::FastGetMethod

@bahusoid
Copy link
Member

What is the performance improvement? :) Old way seems more convenient to use... Mabye Fast should be as suffix - GetMethodFast

@hazzik
Copy link
Member Author

hazzik commented Apr 18, 2020

What is the performance improvement?

It is about 3-3.5 times faster (obviously it is in nanosecond ;-)) (Source)

BenchmarkDotNet=v0.12.1, OS=macOS Catalina 10.15.2 (19C57) [Darwin 19.2.0]
Intel Core i5-4308U CPU 2.80GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.200
  [Host]     : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT
  DefaultJob : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT

Method Mean Error StdDev Ratio
FastGetMethod 296.5 ns 2.51 ns 2.10 ns 0.33
GetMethod 898.1 ns 13.14 ns 12.30 ns 1.00
BenchmarkDotNet=v0.12.1, OS=macOS Catalina 10.15.2 (19C57) [Darwin 19.2.0]
Intel Core i5-4308U CPU 2.80GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.200
  [Host]     : .NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT
  DefaultJob : .NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT

Method Mean Error StdDev Ratio
FastGetMethod 338.2 ns 3.10 ns 2.75 ns 0.29
GetMethod 1,182.9 ns 16.04 ns 15.00 ns 1.00

EDIT: (3.1 results were outdated)

@hazzik
Copy link
Member Author

hazzik commented Apr 18, 2020

Old way seems more convenient to use...

For me it's about the same.

Mabye Fast should be as suffix - GetMethodFast

I thought on naming it like this, but I like FastGetMethod as it feels more natural.

bahusoid
bahusoid previously approved these changes Apr 18, 2020
Copy link
Member

@bahusoid bahusoid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it seems it's really useful only in one place for WithLock extension (should we also move it to static field?). And other places are executed only once. But why not...

@hazzik
Copy link
Member Author

hazzik commented Apr 18, 2020

Actually, with FastGetMethod we might not need the static fields anymore to hold methods.

@fredericDelaporte fredericDelaporte added this to the 5.3 milestone Apr 19, 2020
@hazzik hazzik merged commit 7a8d36d into nhibernate:master Apr 21, 2020
@hazzik hazzik deleted the FastGetMethod branch February 22, 2023 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants