利用表達式樹構建委托改善反射性能


最近搞一個系統時由於在比較關鍵地方用到反射了,所以要關注了一下反射的性能問題。搜索一下,不難搜到老趙的這篇文章,下面是一些雜亂的筆記。(建議先看老趙的文章)

.Net4.0反射性能改善

看老趙的文章,老趙得到的結果是這樣的:

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

而我把代碼搞下來自己運行得到這樣的結果:

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

這里不是說機器性能造成絕對的時間,而是差距比例完全不一樣,想了一陣想起了老趙當時應該是基於.Net3.5,果斷把程序的目標框架切換到.Net3.5,結果如下:

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>其中第一個參數是實例對象,第二是參數列表,第三是返回值。.Net4.0的表達式樹要比3.5的先進一點,經過一番改造發現是不需要這么一個萬能委托的,直接用Expression.Lambda.Compile()編譯出來的Delegate強制轉換為強類型的委托來得更加簡單。全部代碼一個方法即可,精簡了許多。

/// <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();
}

使用時轉換為強類型的委托即可:

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

老趙那個委托都是object,使用時的類型轉換,還有裝箱,拆箱都會有一定的性能損失,而強類型就沒有這個問題。

首先在老趙的那篇文章上一個方法改為兩個方法,然后測試:

public void Call1(object o1, object o2, object o3) { }
public void Call2(int o1, int o2, int o3) { }
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)");
}

結果:

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來表示一個賦值表達式。有了它,構建起來比方法的更加簡單:

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();
}

直接返回了強類型的委托,所以使用起來更加簡單:

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

來測試一下性能:

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)");
}

結果如下:

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。

===重要更新===

我把上面的方法用在項目中才發現陷入了一個悖論。就是往往需要使用反射的上下文中是沒有實例類型的,而有了實例類型的上下文中有往往不需要反射。所以構建表達式樹是需要去掉實例的類型參數,在表達式樹中做類型轉換,這樣會有一點點的性能損失,但同時又帶來一個好處,使用時類型參數少了一個,寫起來方便了一點。兩個主要代碼如下:

/// <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(typeof(object), "instance");
        //((T)instance)
        var castExpression = Expression.Convert(instanceExpression, methodInfo.ReflectedType);
        //((T)instance).Call(params)
        callExpression = Expression.Call(castExpression, methodInfo, paramExpressions);
        paramExpressions.Insert(0, instanceExpression);
    }
    var lambdaExpression = Expression.Lambda(callExpression, paramExpressions);
    return lambdaExpression.Compile();
}

//使用
public static Action<object> BuildAction(MethodInfo methodInfo)
{
    return (Action<object>)BuildDynamicDelegate(methodInfo);
}

public static Action<object, T1> BuildAction<T1>(MethodInfo methodInfo)
{
    return (Action<object, T1>)BuildDynamicDelegate(methodInfo);
}
/// <summary>
/// 動態構造賦值委托
/// </summary>
/// <typeparam name="TProperty">屬性類型</typeparam>
/// <param name="propertyInfo">屬性元數據</param>
/// <returns>強類型委托</returns>
public static Action<object, TProperty> BuildSetPropertyAction<TProperty>(PropertyInfo propertyInfo)
{
    var instanceParam = Expression.Parameter(typeof(object), "instance");
    var valueParam = Expression.Parameter(typeof(TProperty), "value");
    //((T)instance)
    var castExpression = Expression.Convert(instanceParam, propertyInfo.ReflectedType);
    //((T)instance).Property
    var propertyProperty = Expression.Property(castExpression, propertyInfo);
    //((T)instance).Property = value
    var assignExpression = Expression.Assign(propertyProperty, valueParam);
    var lambdaExpression = Expression.Lambda<Action<object, TProperty>>(assignExpression, instanceParam, valueParam);
    return lambdaExpression.Compile();
}

//使用
var action = BuildSetPropertyAction<int>(IntPropertyInfo);
action(ProgramInstance, IntValue);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM