方法的直接调用,反射调用与……Lambda表达式调用

source:http://www.cnblogs.com/JeffreyZhao/archive/2008/11/23/invoke-method-by-lambda-expression.html

方法的直接调用,反射调用与……Lambda表达式调用

Posted on 2008-11-23 19:22 Jeffrey Zhao 阅读(1840) 评论(26)   编辑 收藏 网摘 所属分类: 性能优化.NET 3.5

  想调用一个方法很容易,直接代码调用就行,这人人都会。其次呢,还可以使用反射。不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样。虽然这是个众所周知的现象,我们还是来写个程序来验证一下。比如我们现在新建一个Console应用程序,编写一个最简单的Call方法。

class Program
{
    static void Main(string[] args)
    {
        
    }

    public void Call(object o1, object o2, object o3) { }
}

  Call方法接受三个object参数却没有任何实现,这样我们就可以让测试专注于方法调用,而并非方法实现本身。于是我们开始编写测试代码,比较一下方法的直接调用与反射调用的性能差距:

static void Main(string[] args)
{
    int times = 1000000;
    Program program = new Program();
    object[] parameters = new object[] { new object(), new object(), new object() };
    program.Call(null, null, null); // force JIT-compile

    Stopwatch watch1 = new Stopwatch();
    watch1.Start();
    for (int i = 0; i < times; i++)
    {
        program.Call(parameters[0], parameters[1], parameters[2]);
    }
    watch1.Stop();
    Console.WriteLine(watch1.Elapsed + " (Directly invoke)");

    MethodInfo methodInfo = typeof(Program).GetMethod("Call");
    Stopwatch watch2 = new Stopwatch();
    watch2.Start();
    for (int i = 0; i < times; i++)
    {
        methodInfo.Invoke(program, parameters);
    }
    watch2.Stop();
    Console.WriteLine(watch2.Elapsed + " (Reflection invoke)");

    Console.WriteLine("Press any key to continue...");
    Console.ReadKey();
}

  执行结果如下:

00:00:00.0135323 (Directly invoke)
00:00:05.2325120 (Reflection invoke)
Press any key to continue...

  通过各调用一百万次所花时间来看,两者在性能上具有数量级的差距。因此,很多框架在必须利用到反射的场景中,都会设法使用一些较高级的替代方案来改善性能。例如,使用CodeDom生成代码并动态编译,或者使用Emit来直接编写IL。不过自从.NET 3.5发布了Expression相关的新特性,我们在以上的情况下又有了更方便并直观的解决方案。

  了解Expression相关特性的朋友可能知道,System.Linq.Expressions.Expression<TDelegate>类型的对象在调用了它了Compile方法之后将得到一个TDelegate类型的委托对象,而调用一个委托对象与直接调用一个方法的性能开销相差无几。那么对于上面的情况,我们又该得到什么样的Delegate对象呢?为了使解决方案足够通用,我们必须将各种签名的方法统一至同样的委托类型中,如下:

public Func<object, object[], object> GetVoidDelegate()
{
    Expression<Action<object, object[]>> exp = (instance, parameters) => 
        ((Program)instance).Call(parameters[0], parameters[1], parameters[2]);

    Action<object, object[]> action = exp.Compile();
    return (instance, parameters) =>
    {
        action(action, parameters);
        return null;
    };
}

  如上,我们就得到了一个Func<object, object[], object>类型的委托,这意味它接受一个object类型与object[]类型的参数,以及返回一个object类型的结果——等等,朋友们有没有发现,这个签名与MethodInfo类型的Invoke方法完全一致?不过可喜可贺的是,我们现在调用这个委托的性能远高于通过反射来调用了。那么对于有返回值的方法呢?那构造一个委托对象就更方便了:

public int Call(object o1, object o2) { return 0; }

public Func<object, object[], object> GetDelegate()
{
    Expression<Func<object, object[], object>> exp = (instance, parameters) =>
        ((Program)instance).Call(parameters[0], parameters[1]);

    return exp.Compile();
}

  至此,我想朋友们也已经能够轻松得出调用静态方法的委托构造方式了。可见,这个解决方案的关键在于构造一个合适的Expression<TDelegate>,那么我们现在就来编写一个DynamicExecuter类来作为一个较为完整的解决方案:

public class DynamicExecutor
{
    private Func<object, object[], object> m_execute;

    public DynamicExecutor(MethodInfo methodInfo)
    {
        this.m_execute = this.GetExecuteDelegate(methodInfo);
    }

    public object Execute(object instance, object[] parameters)
    {
        return this.m_execute(instance, parameters);
    }

    private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo)
    {
        // parameters to execute
        ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");
        ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");

        // build parameter list
        List<Expression> parameterExpressions = new List<Expression>();
        ParameterInfo[] paramInfos = methodInfo.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            // (Ti)parameters[i]
            BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
            UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);

            parameterExpressions.Add(valueCast);
        }

        // non-instance for static method, or ((TInstance)instance)
        Expression instanceCast = methodInfo.IsStatic ? null : 
            Expression.Convert(instanceParameter, methodInfo.ReflectedType);

        // static invoke or ((TInstance)instance).Method
        MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);
        
        // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
        if (methodCall.Type == typeof(void))
        {
            Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(
                methodCall, instanceParameter, parametersParameter);

            Action<object, object[]> execute = lambda.Compile();
            return (instance, parameters) =>
            {
                execute(instance, parameters);
                return null;
            };
        }
        else
        {
            UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
            Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(
                castMethodCall, instanceParameter, parametersParameter);

            return lambda.Compile();
        }
    }
}

  DynamicExecutor的关键就在于GetExecuteDelegate方法中构造Expression Tree的逻辑。如果您对于一个Expression Tree的结构不太了解的话,不妨尝试一下使用Expression Tree Visualizer来对一个现成的Expression Tree进行观察和分析。我们将一个MethodInfo对象传入DynamicExecutor的构造函数之后,就能将各组不同的实例对象和参数对象数组传入Execute进行执行。这一切就像使用反射来进行调用一般,不过它的性能就有了明显的提高。例如我们添加更多的测试代码:

DynamicExecutor executor = new DynamicExecutor(methodInfo);
Stopwatch watch3 = new Stopwatch();
watch3.Start();
for (int i = 0; i < times; i++)
{
    executor.Execute(program, parameters);
}
watch3.Stop();
Console.WriteLine(watch3.Elapsed + " (Dynamic executor)");

  现在的执行结果则是:

00:00:00.0148828 (Directly invoke)
00:00:05.2488540 (Reflection invoke)
00:00:00.0618695 (Dynamic executor)
Press any key to continue...

  事实上,Expression<TDelegate>类型的Compile方法正是使用Emit来生成委托对象。不过现在我们已经无需将目光放在更低端的IL上,只要使用高端的API来进行Expression Tree的构造,这无疑是一种进步。不过这种方法也有一定局限性,例如我们只能对公有方法进行调用,并且除了方法外的其他类型成员,我们就无法如上例般惬意地编写代码了。

Feedback

#1楼 [楼主]   回复  引用  查看    

2008-11-22 01:54 by Jeffrey Zhao       
好久没写文章了……

#2楼    回复  引用  查看    

2008-11-22 02:52 by 哈密瓜牌牛奶       
确实是很久了,哈...
不过每篇文章都值得我细细品味:)

#3楼    回复  引用  查看    

2008-11-22 08:59 by volnet(可以叫我大V)       
这么早起就是来占位的!

#4楼    回复  引用  查看    

2008-11-22 09:23 by 李胜攀       
来抢座啦
总算是前排的一次哈。

#5楼    回复  引用  查看    

2008-11-22 09:31 by 狼Robot       
学习

#6楼    回复  引用  查看    

2008-11-22 09:43 by Joyaspx       
一直关注着你的文章,呵呵

#7楼    回复  引用  查看    

2008-11-22 10:01 by 紫色永恒       
忙着打理衣橱吧...

#8楼    回复  引用  查看    

2008-11-22 10:05 by Astar       
#199楼 回复 引用 查看 删除 修改
2008-11-16 16:29 by Astar
麻烦老赵能不能把"ASP.NET MVC框架开发系列课程"中的第(8):AJAX中的DEMO给我发一下呢.......谢谢老赵!! passvcword@126.com
--------------------------
这是我给你留的言,不知道看道没有,能抽空给我发一份吗,谢谢.

#9楼    回复  引用  查看    

2008-11-22 10:46 by Anytao       
好,这才有意思:-)

#10楼    回复  引用  查看    

2008-11-22 11:20 by Anders Cui       
不错,看到lambda就喜欢 :)

#11楼    回复  引用  查看    

2008-11-22 12:50 by JimLiu       
支持,Func编译后性能还是很不错的

#12楼    回复  引用  查看    

2008-11-22 13:24 by 封士勇       
.net 2.0 可以这么用吗?

#13楼    回复  引用  查看    

2008-11-22 14:01 by bidaas       
分析表达式树是否会较大影响性能呢?

#14楼 [楼主]   回复  引用  查看    

2008-11-22 14:38 by Jeffrey Zhao       
--引用--------------------------------------------------
哈密瓜牌牛奶: 确实是很久了,哈...
不过每篇文章都值得我细细品味:)
--------------------------------------------------------
多谢支持

#15楼 [楼主]   回复  引用  查看    

2008-11-22 14:39 by Jeffrey Zhao       
--引用--------------------------------------------------
volnet(可以叫我大V): 这么早起就是来占位的!
--------------------------------------------------------
沙发是我的,恩

#16楼 [楼主]   回复  引用  查看    

2008-11-22 14:40 by Jeffrey Zhao       
--引用--------------------------------------------------
紫色永恒: 忙着打理衣橱吧...
--------------------------------------------------------
其实大部分原因还是又懒又笨……

#17楼 [楼主]   回复  引用  查看    

2008-11-22 14:40 by Jeffrey Zhao       
--引用--------------------------------------------------
Anders Cui: 不错,看到lambda就喜欢 :)
--------------------------------------------------------
我打赌,那个死硬派臭包子还是不接受

#18楼 [楼主]   回复  引用  查看    

2008-11-22 14:41 by Jeffrey Zhao       
--引用--------------------------------------------------
JimLiu: 支持,Func编译后性能还是很不错的
--------------------------------------------------------
Emit后就变成一个普通的委托调用了

#19楼 [楼主]   回复  引用  查看    

2008-11-22 14:41 by Jeffrey Zhao       
--引用--------------------------------------------------
封士勇: .net 2.0 可以这么用吗?
--------------------------------------------------------
很遗憾,不可以……

#20楼 [楼主]   回复  引用  查看    

2008-11-22 14:41 by Jeffrey Zhao       
--引用--------------------------------------------------
bidaas: 分析表达式树是否会较大影响性能呢?
--------------------------------------------------------
不会的,其实就是一棵树的普通遍历而已,一般这棵树都不会很大。

#21楼 [楼主]   回复  引用  查看    

2008-11-22 14:46 by Jeffrey Zhao       
@Astar
发了

#22楼    回复  引用  查看    

2008-11-22 14:55 by Astar       
谢谢你

#23楼    回复  引用  查看    

2008-11-22 15:20 by 上不了岸的鱼{ttzhang}       
虽然不大明白后面的内容,还是顶一下楼主

#24楼    回复  引用    

2008-11-22 21:43 by andywu [未注册用户]
very good. thx

#25楼 [楼主]   回复  引用  查看    

2008-11-23 19:31 by Jeffrey Zhao       
--引用--------------------------------------------------
上不了岸的鱼{ttzhang}: 虽然不大明白后面的内容,还是顶一下楼主
--------------------------------------------------------
多谢支持
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值