• 利用表达式树构建委托改善反射性能


    最近搞一个系统时由于在比较关键地方用到反射了,所以要关注了一下反射的性能问题。搜索一下,不难搜到老赵的这篇文章,下面是一些杂乱的笔记。(建议先看老赵的文章)

    .Net4.0反射性能改善

    看老赵的文章,老赵得到的结果是这样的:

    1
    2
    3
    00:00:00.0125539 (Directly invoke)
    00:00:04.5349626 (Reflection invoke)
    00:00:00.0322555 (Dynamic executor)

    而我把代码搞下来自己运行得到这样的结果:

    1
    2
    3
    00:00:00.0009710 (Directly invoke)
    00:00:00.4142893 (Reflection invoke)
    00:00:00.0194501 (Dynamic executor)

    这里不是说机器性能造成绝对的时间,而是差距比例完全不一样,想了一阵想起了老赵当时应该是基于.Net3.5,果断把程序的目标框架切换到.Net3.5,结果如下:

    1
    2
    3
    00:00:00.0018801 (Directly invoke)
    00:00:02.4288876 (Reflection invoke)
    00:00:00.0141537 (Dynamic executor)
    三者的差距仍然有些不一样,老赵那边的直接调用与动态执行同一数量级的结果还是没有。但发现了另一些信息。反射和直接调用方法.Net4.0比.Net3.5有非常大的改善,特别是反射,性能提升了好几倍。反而构建表达式树动态调用的方式性能比.Net3.5差了一点。但是相对反射还是有差距,按照这个比例,写写表达式树还是值得的。

    改善老赵的DynamicMethodExecutor

    老赵的那篇的文章的思路是使用DynamicMethodExecutor来构造一个万能的委托Func<object, object[],="" object=""><object, object[], object>其中第一个参数是实例对象,第二是参数列表,第三是返回值。.Net4.0的表达式树要比3.5的先进一点,经过一番改造发现是不需要这么一个万能委托的,直接用Expression.Lambda.Compile()编译出来的Delegate强制转换为强类型的委托来得更加简单。全部代码一个方法即可,精简了许多。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /// <summary>
    /// 动态构造委托
    /// </summary>
    /// <param name="methodInfo">方法元数据</param>
    /// <returns>委托</returns>
    public static Delegate BuildDynamicDelegate(MethodInfo methodInfo)
    {
        if (methodInfo == null)
            throw new ArgumentNullException("methodInfo");
     
        var paramExpressions = methodInfo.GetParameters().Select((p, i) =>
        {
            var name = "param" + (i + 1).ToString(CultureInfo.InvariantCulture);
            return Expression.Parameter(p.ParameterType, name);
        }).ToList();
     
        MethodCallExpression callExpression;
        if (methodInfo.IsStatic)
        {
            //Call(params....)
            callExpression = Expression.Call(methodInfo, paramExpressions);
        }
        else
        {
            var instanceExpression = Expression.Parameter(methodInfo.ReflectedType, "instance");
            //insatnce.Call(params….)
            callExpression = Expression.Call(instanceExpression, methodInfo, paramExpressions);
            paramExpressions.Insert(0, instanceExpression);
        }
        var lambdaExpression = Expression.Lambda(callExpression, paramExpressions);
        return lambdaExpression.Compile();
    }

    使用时转换为强类型的委托即可:

    1
    2
    var action = (Action<TInstance, T1, T2>)BuildDynamicDelegate(methodInfo);
    var func = (Func<TInstance, T1, T2, TReturn>)BuildDynamicDelegate(methodInfo);

    老赵那个委托都是object,使用时的类型转换,还有装箱,拆箱都会有一定的性能损失,而强类型就没有这个问题。

    首先在老赵的那篇文章上一个方法改为两个方法,然后测试:

    1
    2
    public void Call1(object o1, object o2, object o3) { }
    public void Call2(int o1, int o2, int o3) { }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    private static void DynamicExecutor_ObjectType()
    {
        var executor = new DynamicMethodExecutor(Call1MethodInfo);
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i < Times; i++)
        {
            executor.Execute(ProgramInstance, ObjectParameters);
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Dynamic executor(object))(JeffreyZhao)");
    }
    private static void DynamicExecutor_IntType()
    {
        var executor = new DynamicMethodExecutor(Call2MethodInfo);
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i < Times; i++)
        {
            executor.Execute(ProgramInstance, IntParameters);
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Dynamic executor(int))(JeffreyZhao)");
    }
    private static void DynamicExecutor_StrongObject()
    {
        var action = DynamicMethodBuilder.BuildAction<Program, object, object, object>(Call1MethodInfo);
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i < Times; i++)
        {
            action(ProgramInstance, ObjectParameters[0], ObjectParameters[1], ObjectParameters[2]);
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Dynamic executor(object))(zhangweiwen)");
    }
     
    private static void DynamicExecutor_StrongInt()
    {
        var action = DynamicMethodBuilder.BuildAction<Program, int, int, int>(Call2MethodInfo);
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i < Times; i++)
        {
            action(ProgramInstance, IntParameters1[0], IntParameters1[1], IntParameters1[2]);
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Dynamic executor(int))(zhangweiwen)");
    }

    结果:

    1
    2
    3
    4
    00:00:00.0188422 (Dynamic executor(object))(JeffreyZhao)
    00:00:00.0210869 (Dynamic executor(int))(JeffreyZhao)
    00:00:00.0142841 (Dynamic executor(object))(zhangweiwen)
    00:00:00.0147589 (Dynamic executor(int))(zhangweiwen)

    差距不大,但是还是有一定得改善,特别参数是int的方法,用了强类型后性能比较稳定,不会出现偏差。

    构建委托动态赋值

    既然有动态调用方法,同样也可以动态赋值,而且据我的经验,根据PropertyInfo的SetValue去反射设属性值用得比反射调用方法更加频繁。所以同样需要有方法来动态构建委托改善性能。

    幸好,.Net4.0提供了支持,.Net4.0新增了Expression.Assign来表示一个赋值表达式。有了它,构建起来比方法的更加简单:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private static Action<TInstance, TProperty> BuildSetPropertyAction<TInstance, TProperty>(PropertyInfo propertyInfo)
    {
        var instanceParam = Expression.Parameter(typeof(TInstance), "instance");
        var valueParam = Expression.Parameter(typeof(TProperty), "value");
        //instance.Property
        var propertyProperty = Expression.Property(instanceParam, propertyInfo);
        //instance.Property = value
        var assignExpression = Expression.Assign(propertyProperty, valueParam);
        var lambdaExpression = Expression.Lambda<Action<TInstance, TProperty>>(assignExpression, instanceParam, valueParam);
        return lambdaExpression.Compile();
    }

    直接返回了强类型的委托,所以使用起来更加简单:

    1
    2
    var action = BuildSetPropertyAction<Program, object>(ObjectPropertyInfo);
    action(ProgramInstance, ObjectValue);

    来测试一下性能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    private static void DirectlySetValueType()
    {
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i &lt; Times; i++)
        {
            ProgramInstance.IntProperty = IntValue;
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Directly Set IntProperty)");
    }
     
    private static void ReflectionSetValueType()
    {
        var watch2 = new Stopwatch();
        watch2.Start();
        for (var i = 0; i &lt; Times; i++)
        {
            IntPropertyInfo.SetValue(ProgramInstance, IntValue, null);
        }
        watch2.Stop();
        Console.WriteLine(watch2.Elapsed + " (Reflection Set IntProperty)");
    }
     
    private static void DynamicSetValueType()
    {
        var action = BuildSetPropertyAction&lt;Program, int&gt;(IntPropertyInfo);
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i &lt; Times; i++)
        {
            action(ProgramInstance, IntValue);
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Dynamic Set IntProperty)");
    }
     
    private static void DirectlySetReferenceType()
    {
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i &lt; Times; i++)
        {
            ProgramInstance.ObjectProperty = ObjectValue;
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Directly Set ObjectProperty)");
    }
     
    private static void ReflectionSetReferenceType()
    {
        var watch2 = new Stopwatch();
        watch2.Start();
        for (var i = 0; i &lt; Times; i++)
        {
            ObjectPropertyInfo.SetValue(ProgramInstance, ObjectValue, null);
        }
        watch2.Stop();
        Console.WriteLine(watch2.Elapsed + " (Reflection Set ObjectProperty)");
    }
     
    private static void DynamicSetReferenceType()
    {
        var action = BuildSetPropertyAction&lt;Program, object&gt;(ObjectPropertyInfo);
        //action(ProgramInstance, ObjectValue);
        var watch1 = new Stopwatch();
        watch1.Start();
        for (var i = 0; i &lt; Times; i++)
        {
            action(ProgramInstance, ObjectValue);
        }
        watch1.Stop();
        Console.WriteLine(watch1.Elapsed + " (Dynamic Set ObjectProperty)");
    }

    结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    Test Set Value:
    00:00:00.0003237 (Directly Set IntProperty)
    00:00:00.3160570 (Reflection Set IntProperty)
    00:00:00.0132668 (Dynamic Set IntProperty)
    -----
    00:00:00.0028183 (Directly Set ObjectProperty)
    00:00:00.2937783 (Reflection Set ObjectProperty)
    00:00:00.0150118 (Dynamic Set ObjectProperty)

    虽然跟直接赋值不能比,但比反射快大概30倍。

    全部代码

    希望对大家有帮助

    The End。

     http://www.cnblogs.com/lemontea/archive/2013/02/04/2891281.html

  • 相关阅读:
    多线程编程之线程死锁问题 转载
    线程的挂起和恢复 转载
    redis 内存库设置 教你怎么解决64位Windows版Redis狂占C盘的问题.
    MSSQL数据库分区表
    如何将桌面的路径定义到其它盘符,如d:users桌面
    sqlserver数据库脱机时发生异常:由于无法在数据库 'SMS' 上放置锁,ALTER DATABASE 失败。请稍后再试。 ALTER DATABASE 语句失败。 (.Net SqlClient Data Provider)
    JAVA代码反编译笔记
    SQLServer—系统中的内存配置
    .net remoting 客户端与服务端绑定事件,一部电脑当服务器,另一部当客户端,发布后没法接收远程错误信息。
    Java Socket 模拟HTTP请求
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2891839.html
Copyright © 2020-2023  润新知