Hello World
Spiga

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

2008-11-24 09:59 by 老赵, 35277 visits

想调用一个方法很容易,直接代码调用就行,这人人都会。其次呢,还可以使用反射。不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样。虽然这是个众所周知的现象,我们还是来写个程序来验证一下。比如我们现在新建一个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.0119041 (Directly invoke)
00:00:04.5527141 (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(instance, 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 DynamicMethodExecutor
{
    private Func<object, object[], object> m_execute;

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

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

DynamicMethodExecutor executor = new DynamicMethodExecutor(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.0125539 (Directly invoke)
00:00:04.5349626 (Reflection invoke)
00:00:00.0322555 (Dynamic executor)
Press any key to continue...

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

补充

木野狐兄在评论中引用了Code Project的文章《A General Fast Method Invoker》,其中通过Emit构建了FastInvokeHandler委托对象(其签名与Func<object, object[], object>完全相同)的调用效率似乎较“方法直接”调用的性能更高(虽然从原文示例看来并非如此)。事实上FastInvokeHandler其内部实现与DynamicMethodExecutor完全相同,居然有如此令人不可思议的表现实在让人啧啧称奇。我猜测,FastInvokeHandler与DynamicMethodExecutor的性能优势可能体现在以下几个方面:

  1. 范型委托类型的执行性能较非范型委托类型略低(求证)。
  2. 多了一次Execute方法调用,损失部分性能。
  3. 生成的IL代码更为短小紧凑。
  4. 木野狐兄没有使用Release模式编译。:P

不知道是否有对此感兴趣的朋友能够再做一个测试,不过请注意此类性能测试一定需要在Release编译下进行(这点很容易被忽视),否则意义其实不大。

此外,我还想强调的就是,本篇文章进行是纯技术上的比较,并非在引导大家追求点滴性能上的优化。有时候看到一些关于比较for或foreach性能优劣的文章让许多朋友都纠结与此,甚至搞得面红耳赤,我总会觉得有些无可奈何。其实从理论上来说,提高性能的方式有许许多多,记得当时在大学里学习Introduction to Computer System这门课时得一个作业就是为一段C程序作性能优化,当时用到不少手段,例如内联方法调用以减少CPU指令调用次数、调整循环嵌套顺序以提高CPU缓存命中率,将一些代码使用内嵌ASM替换等等,可谓“无所不用其极”,大家都在为几个时钟周期的性能提高而发奋图强欢呼雀跃……

那是理论,是在学习。但是在实际运用中,我们还必须正确对待学到的理论知识。我经常说的一句话是:“任何应用程序都会有其性能瓶颈,只有从性能瓶颈着手才能做到事半功倍的结果。”例如,普通Web应用的性能瓶颈往往在外部IO(尤其是数据库读写),要真正提高性能必须从此入手(例如数据库调优,更好的缓存设计)。正因如此,开发一个高性能的Web应用程序的关键不会在语言或语言运行环境上,.NET、RoR、PHP、Java等等在这一领域都表现良好。

This article
Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

112 条回复

  1. 老赵
    admin
    链接

    老赵 2008-11-22 01:54:00

    好久没写文章了……

  2. 哈密瓜牌牛奶
    *.*.*.*
    链接

    哈密瓜牌牛奶 2008-11-22 02:52:00

    确实是很久了,哈...
    不过每篇文章都值得我细细品味:)

  3. volnet(可以叫我大V)
    *.*.*.*
    链接

    volnet(可以叫我大V) 2008-11-22 08:59:00

    这么早起就是来占位的!

  4. 李胜攀
    *.*.*.*
    链接

    李胜攀 2008-11-22 09:23:00

    来抢座啦
    总算是前排的一次哈。

  5. 狼Robot
    *.*.*.*
    链接

    狼Robot 2008-11-22 09:31:00

    学习

  6. Joyaspx
    *.*.*.*
    链接

    Joyaspx 2008-11-22 09:43:00

    一直关注着你的文章,呵呵

  7. 紫色永恒
    *.*.*.*
    链接

    紫色永恒 2008-11-22 10:01:00

    忙着打理衣橱吧...

  8. Astar
    *.*.*.*
    链接

    Astar 2008-11-22 10:05:00

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

  9. Anytao
    *.*.*.*
    链接

    Anytao 2008-11-22 10:46:00

    好,这才有意思:-)

  10. Anders Cui
    *.*.*.*
    链接

    Anders Cui 2008-11-22 11:20:00

    不错,看到lambda就喜欢 :)

  11. JimLiu
    *.*.*.*
    链接

    JimLiu 2008-11-22 12:50:00

    支持,Func编译后性能还是很不错的

  12. 封士勇
    *.*.*.*
    链接

    封士勇 2008-11-22 13:24:00

    .net 2.0 可以这么用吗?

  13. bidaas
    *.*.*.*
    链接

    bidaas 2008-11-22 14:01:00

    分析表达式树是否会较大影响性能呢?

  14. 老赵
    admin
    链接

    老赵 2008-11-22 14:38:00

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

  15. 老赵
    admin
    链接

    老赵 2008-11-22 14:39:00

    --引用--------------------------------------------------
    volnet(可以叫我大V): 这么早起就是来占位的!
    --------------------------------------------------------
    沙发是我的,恩

  16. 老赵
    admin
    链接

    老赵 2008-11-22 14:40:00

    --引用--------------------------------------------------
    Anders Cui: 不错,看到lambda就喜欢 :)
    --------------------------------------------------------
    我打赌,那个死硬派臭包子还是不接受

  17. 老赵
    admin
    链接

    老赵 2008-11-22 14:40:00

    --引用--------------------------------------------------
    紫色永恒: 忙着打理衣橱吧...
    --------------------------------------------------------
    其实大部分原因还是又懒又笨……

  18. 老赵
    admin
    链接

    老赵 2008-11-22 14:41:00

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

  19. 老赵
    admin
    链接

    老赵 2008-11-22 14:41:00

    --引用--------------------------------------------------
    封士勇: .net 2.0 可以这么用吗?
    --------------------------------------------------------
    很遗憾,不可以……

  20. 老赵
    admin
    链接

    老赵 2008-11-22 14:41:00

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

  21. 老赵
    admin
    链接

    老赵 2008-11-22 14:46:00

    @Astar
    发了

  22. Astar
    *.*.*.*
    链接

    Astar 2008-11-22 14:55:00

    谢谢你

  23. 上不了岸的鱼{ttzhang}
    *.*.*.*
    链接

    上不了岸的鱼{ttzhang} 2008-11-22 15:20:00

    虽然不大明白后面的内容,还是顶一下楼主

  24. andywu[未注册用户]
    *.*.*.*
    链接

    andywu[未注册用户] 2008-11-22 21:43:00

    very good. thx

  25. 老赵
    admin
    链接

    老赵 2008-11-23 19:31:00

    --引用--------------------------------------------------
    上不了岸的鱼{ttzhang}: 虽然不大明白后面的内容,还是顶一下楼主
    --------------------------------------------------------
    多谢支持

  26. Indigo Dai
    *.*.*.*
    链接

    Indigo Dai 2008-11-23 19:45:00

    支持,你好久不发文了,喜欢看你的文章,有新意有深度。博客园里这样的文章多点就好了。老赵再不发文,名气都给别人抢了。

  27. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-23 20:11:00

    动态构造 lambda compile 得到 delegate 确实好写多了。
    不过,单纯从性能讲还是 emit IL 快的多。

    00:00:00.0143448 (Directly invoke)
    00:00:04.8982130 (Reflection invoke)
    00:00:00.0573491 (Dynamic executor)
    00:00:00.0025352 (Fast Invoker)
    Press any key to continue...

    这里最后一个结果是我用网上另一个文章的代码测试的:
    http://www.codeproject.com/KB/cs/FastMethodInvoker.aspx

    比直接调用还快。。。

  28. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-23 20:12:00

    另外我的 cpu 也比你快一点点哦 :D

  29. xjb
    *.*.*.*
    链接

    xjb 2008-11-23 20:22:00

    老赵出手就不一样,很有难度。

    另:好久没看见老赵了

  30. 我是个地道的菜鸟[未注册用户]
    *.*.*.*
    链接

    我是个地道的菜鸟[未注册用户] 2008-11-23 20:22:00

    学习中

  31. Leven
    *.*.*.*
    链接

    Leven 2008-11-23 21:15:00

    支持老赵...

  32. 阿不
    *.*.*.*
    链接

    阿不 2008-11-23 22:29:00

    一种很好的使用体验。

  33. 老赵
    admin
    链接

    老赵 2008-11-23 22:32:00

    --引用--------------------------------------------------
    Indigo Dai: 支持,你好久不发文了,喜欢看你的文章,有新意有深度。博客园里这样的文章多点就好了。老赵再不发文,名气都给别人抢了。
    --------------------------------------------------------
    但是如果没有“灵感”就写不出啊……

  34. 老赵
    admin
    链接

    老赵 2008-11-23 22:34:00

    --引用--------------------------------------------------
    木野狐(Neil Chen): 动态构造 lambda compile 得到 delegate 确实好写多了。
    不过,单纯从性能讲还是 emit IL 快的多。

    00:00:00.0143448 (Directly invoke)
    00:00:04.8982130 (Reflection invoke)
    00:00:00.0573491 (Dynamic executor)
    00:00:00.0025352 (Fast Invoker)
    Press any key to continue...

    这里最后一个结果是我用网上另一个文章的代码测试的:
    http://www.codeproject.com/KB/cs/FastMethodInvoker.aspx

    比直接调用还快。。。
    --------------------------------------------------------
    精简几个IL指令还是有效果的,不过看到你这个例子我想到在学校的时候上Introduction to Computer System课程时是怎么优化程序性能的了,哈哈……
    // 另外,问一下你是使用Release编译下测试的吗?

  35. 老赵
    admin
    链接

    老赵 2008-11-23 22:35:00

    @xjb
    @Leven
    @阿不
    多谢支持

  36. TerryLee
    *.*.*.*
    链接

    TerryLee 2008-11-23 23:30:00

    怎么今天流行修改文章的发布时间,呵呵:)

  37. 老赵
    admin
    链接

    老赵 2008-11-23 23:53:00

    @TerryLee
    明天我打算补充几句话,估计还会修改一次,哈哈。

  38. 怪怪
    *.*.*.*
    链接

    怪怪 2008-11-24 04:03:00

    @木野狐(Neil Chen)
    @Jeffrey Zhao
    CodeProject为数不多的中国人写的精品文章。一直在用一个自己修改的版本~,感觉用Emit调用方法的都应该看看这个。

    另外一个更早开始使用就是自己写的一个类似于WebPart里的PropertyAccessor(还是叫什么来着,内置的)的东东,原理类似不过用在属性上比较方便

    这两玩意轮番上阵,自由~~~

  39. 完全实况
    *.*.*.*
    链接

    完全实况 2008-11-24 08:18:00

    感谢老赵的精品文章。我已经将DynamicExecutor用于我的remoting反射调用BLL层的入口方法中。代替以前一直用的直接反射调用方法。效果不错。

  40. 拼命占便宜
    *.*.*.*
    链接

    拼命占便宜 2008-11-24 09:02:00

    哇! 高手!..

  41. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-24 10:01:00

    to Jeffery,
    // 另外,问一下你是使用Release编译下测试的吗?

    我用的 debug 配置测试的。

  42. 代震军
    *.*.*.*
    链接

    代震军 2008-11-24 10:11:00

    呵呵,不错,先收了:)

  43. 玉开
    *.*.*.*
    链接

    玉开 2008-11-24 10:16:00

    文章 Posted on 2008-11-24 09:59
    第一个回复 2008-11-22 01:54 by Jeffrey Zhao
    咋回事?

  44. 老赵
    admin
    链接

    老赵 2008-11-24 10:29:00

    --引用--------------------------------------------------
    玉开: 文章 Posted on 2008-11-24 09:59
    第一个回复 2008-11-22 01:54 by Jeffrey Zhao
    咋回事?
    --------------------------------------------------------
    看到下面的补充没?刚加的。

  45. 老赵
    admin
    链接

    老赵 2008-11-24 10:30:00

    --引用--------------------------------------------------
    木野狐(Neil Chen): to Jeffery,
    // 另外,问一下你是使用Release编译下测试的吗?

    我用的 debug 配置测试的。
    --------------------------------------------------------
    release下呢?

  46. 老赵
    admin
    链接

    老赵 2008-11-24 10:36:00

    --引用--------------------------------------------------
    怪怪:
    另外一个更早开始使用就是自己写的一个类似于WebPart里的PropertyAccessor(还是叫什么来着,内置的)的东东,原理类似不过用在属性上比较方便

    这两玩意轮番上阵,自由~~~
    --------------------------------------------------------
    Emit还是不方便,简单的调用方法,设置属性之类还好,复杂点就变直接写汇编了……

  47. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-24 10:39:00

    to Jeff,

    release
    ===========
    00:00:00.0099618 (Directly invoke)
    00:00:03.8768231 (Reflection invoke)
    00:00:00.0309299 (Dynamic executor)
    00:00:00.0023924 (Fast Invoker)
    Press any key to continue...

    更快些.

  48. 老赵
    admin
    链接

    老赵 2008-11-24 10:50:00

    @木野狐(Neil Chen)
    为啥那片文章上还是Direct Invoke快啊?而且也没道理Fast Invoke会那么快。

  49. TaranChen
    *.*.*.*
    链接

    TaranChen 2008-11-24 10:57:00

    老赵一大早就写文章,不一样就是不一样!

  50. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-24 11:14:00

    @Jeffrey Zhao
    会不会是产生的 delegate 调用时有了某种缓存机制呢?
    这个内部的原理我还没研究过。你可以拿那个代码套进你的例子测试一下,确实要快些。

  51. 玉开
    *.*.*.*
    链接

    玉开 2008-11-24 11:27:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------

    玉开: 文章 Posted on 2008-11-24 09:59

    第一个回复 2008-11-22 01:54 by Jeffrey Zhao

    咋回事?

    --------------------------------------------------------

    看到下面的补充没?刚加的。
    --------------------------------------------------------
    补充点内容就可以作为一篇新随笔发出来?我咋没发现有这个功能呢?

  52. 老赵
    admin
    链接

    老赵 2008-11-24 11:29:00

    @木野狐(Neil Chen)
    我估计就是简化了IL吧,找机会我自己观察分析一下……

  53. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-24 11:55:00

    老赵是个好同志,很扎实很强大!呵呵
    每次从你这里都能学到东西.

  54. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-24 11:56:00

    看到你补充的部分了,说的很中肯。
    我也只是想起来看过那篇文章所以对比一下,实际并不一定需要那么用。

  55. 朝晖的.net
    *.*.*.*
    链接

    朝晖的.net 2008-11-24 11:58:00


    顶一下,了解到更多的内容。

  56. Anders Liu
    *.*.*.*
    链接

    Anders Liu 2008-11-24 11:59:00

    9:59发的,11:59已经盖了50多搂了,老赵果然强悍……

  57. 老赵
    admin
    链接

    老赵 2008-11-24 12:04:00

    --引用--------------------------------------------------
    木野狐(Neil Chen): 看到你补充的部分了,说的很中肯。
    我也只是想起来看过那篇文章所以对比一下,实际并不一定需要那么用。
    --------------------------------------------------------
    没有没有,没有说你怎么样,只是顺便谈了一下……

  58. 老赵
    admin
    链接

    老赵 2008-11-24 12:05:00

    --引用--------------------------------------------------
    Anders Liu: 9:59发的,11:59已经盖了50多搂了,老赵果然强悍……
    --------------------------------------------------------
    是早上补充东西所以更新了一下修改时间,文章是周六早上发的……

  59. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2008-11-24 12:09:00

    @Jeffrey Zhao

    赫赫我没有那样想啦,我也只是表达我的观点。

  60. 要有好的心情
    *.*.*.*
    链接

    要有好的心情 2008-11-24 12:45:00

    卢彦:五农民出狗狗 原来csdn上的
    http://blog.joycode.com/5drush/
    http://www.cnblogs.com/linkin/
    codeproject上的文章就是他写的吧

  61. 老赵
    admin
    链接

    老赵 2008-11-24 13:43:00

    @要有好的心情
    比我还更新的少呢

  62. 淡忘的记忆[未注册用户]
    *.*.*.*
    链接

    淡忘的记忆[未注册用户] 2008-11-24 15:04:00

    .net 工作流开发架构
    欢迎访问:http://www.feifanit.com.cn/productFlow.htm

  63. 怪怪
    *.*.*.*
    链接

    怪怪 2008-11-24 15:10:00

    @Jeffrey Zhao
    卢彦的那个FastInvoker还有我说的PropertyAccessor,就像你说的使用起来和你这个没什么不同;甚至我们还可以让他们更好用一些。说到其它应用Emit的领域,实际上从元编程的角度,将较为普遍的情况写成通用的解决方法,往往并不会影响开发效率。

    至于现在广泛认可的“不成熟的优化是罪恶之源”,这句话其实还有后半句的,具体怎么说我想不起来了。总而言之,为了几个时钟周期,从来不是理论上的事情。就如Knuth说的,对于大规模循环内部的一个操作来说,对于优化一次运行成千上万次的程序来说,这都是非常有价值的事情。

    而且好的、避免低性能的做法,往往能形成一定的模式,比如你说的内联、调整循环,不少情况都是有一定经验后,到具体问题的第一次编写时,就可以做出决定的,并不会真正花什么时间。仅仅是和硬件相关性特别大的优化,对于咱们这些做软件的,就离得比较远了。

    说到底,这还是一个成本与收益的问题,而不是一个单纯有没有必要的问题 :)

  64. 老赵
    admin
    链接

    老赵 2008-11-24 15:15:00

    @怪怪
    那是,有通用的组件当然不会带来开发性能上的降低了。
    上面这段话主要讲给某些还在纠结于for与foreach问题上,ORM是否降低性能该不该用的问题上……的朋友们。

  65. AlexLiu
    *.*.*.*
    链接

    AlexLiu 2008-11-24 17:55:00

    来晚了。

  66. 老赵
    admin
    链接

    老赵 2008-11-24 18:08:00

    @AlexLiu
    客气

  67. Ray Wu
    *.*.*.*
    链接

    Ray Wu 2008-11-24 21:09:00

    呵呵,感觉离上次看你的文章快一个月了。赞一个

  68. 飞飞双双
    *.*.*.*
    链接

    飞飞双双 2008-11-24 22:02:00

    好久没看你的文章了,感觉文笔又犀利和诙谐了不少哦···
    顶了

  69. SPARON
    *.*.*.*
    链接

    SPARON 2008-11-24 22:03:00

    晕,我的配置和你一样,而且我CPU还升到了T7300,怎么我Dynamic executor就比你慢呢?.0421453郁闷。。。

  70. 老赵
    admin
    链接

    老赵 2008-11-24 22:07:00

    --引用--------------------------------------------------
    飞飞双双: 好久没看你的文章了,感觉文笔又犀利和诙谐了不少哦&#183;&#183;&#183;
    顶了
    --------------------------------------------------------
    多谢支持

  71. 老赵
    admin
    链接

    老赵 2008-11-24 22:07:00

    --引用--------------------------------------------------
    Ray Wu: 呵呵,感觉离上次看你的文章快一个月了。赞一个
    --------------------------------------------------------
    何止……

  72. 老赵
    admin
    链接

    老赵 2008-11-24 22:08:00

    --引用--------------------------------------------------
    SPARON: 晕,我的配置和你一样,而且我CPU还升到了T7300,怎么我Dynamic executor就比你慢呢?.0421453郁闷。。。
    --------------------------------------------------------
    Release模式?
    还有你怎么知道配置和我一样……

  73. Kevin Zou
    *.*.*.*
    链接

    Kevin Zou 2008-11-25 08:21:00

    喜歡樓主的文章,支持

  74. BZZ
    *.*.*.*
    链接

    BZZ 2008-11-25 08:42:00

    支持 慢慢看

  75. 老赵
    admin
    链接

    老赵 2008-11-25 10:12:00

    --引用--------------------------------------------------
    Kevin Zou: 喜歡樓主的文章,支持
    --------------------------------------------------------
    --引用--------------------------------------------------
    BZZ: 支持 慢慢看
    --------------------------------------------------------
    谢谢

  76. Areyan
    *.*.*.*
    链接

    Areyan 2008-11-25 13:36:00

    看老趙的文章,不止能在技術上學習,還總能看到一些發人心省的地方。

  77. 老赵
    admin
    链接

    老赵 2008-11-25 14:19:00

    @Areyan
    您太客气了

  78. 匿名用户[未注册用户]
    *.*.*.*
    链接

    匿名用户[未注册用户] 2008-11-28 18:31:00

    牛!

  79. guide[未注册用户]
    *.*.*.*
    链接

    guide[未注册用户] 2008-12-21 15:37:00

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


    这里的action(action, parameters);好像错了吧

  80. 老赵
    admin
    链接

    老赵 2008-12-21 16:37:00

    @guide
    谢谢,应该是action(instance, parameters)

  81. ITAres
    *.*.*.*
    链接

    ITAres 2009-01-08 23:19:00

    有个问题想问老赵,希望老赵可以回答我:
    MethodInfo methodInfo = typeof(Program).GetMethod("Call");
    -----------我一直以为这一步的时候就进行了反射操作,看你的文章应该是没有的....

    methodInfo.Invoke(program, parameters);
    ------------你的意思是这一步的时候才进行了反射,是吗?

  82. kkun
    *.*.*.*
    链接

    kkun 2009-01-11 16:06:00

    --引用--------------------------------------------------
    ITAres: 有个问题想问老赵,希望老赵可以回答我:
    MethodInfo methodInfo = typeof(Program).GetMethod(&quot;Call&quot;);
    -----------我一直以为这一步的时候就进行了反射操作,看你的文章应该是没有的....

    methodInfo.Invoke(program, parameters);
    ------------你的意思是这一步的时候才进行了反射,是吗?


    --------------------------------------------------------

    我地个神哪,也想问这个问题来着,正在实践赵老师的文章,也发现了这个问题,没想到有人比我快哈,
    个人感觉methodInfo实际上已经用到反射了,只不过此时的反射相比较于后边的Invoke方法性能损失很小,可以忽略,影响性能部分在Invoke而不在一次简单的GetMethod,不知道理解得对不对,

  83. 老赵
    admin
    链接

    老赵 2009-01-11 16:28:00

    @kkun
    不错,而且MethodInfo可以缓存,因此只需要取一次就够了,所以完全可以忽略不计。

  84. kkun
    *.*.*.*
    链接

    kkun 2009-01-12 10:01:00

    赵老师你好!
    这篇文章偶是反复阅读,希望自己能消化掉
    接上次回复说,
    MethodInfo methodInfo = typeof(Program).GetMethod("Call");
    这里已经使用了反射技术,
    但它并不是非常影响性能,因为Invoke方法被替代了,

    但思考另外一种应用场景时,
    如我想把这段代码更新成您的这种方式
    public class ModelHelper<T> where T : new()
    {
    public static T ConvertModel( IDataReader reader )
    {
    T t = new T();
    Type modelType = t.GetType();
    foreach( PropertyInfo p in modelType.GetProperties() )
    {
    p.SetValue( t, GetDefaultValue( reader[p.Name], p.PropertyType ), null );
    }
    return t;
    }

    private static object GetDefaultValue( object obj, Type type )
    {
    if( obj == DBNull.Value )
    {
    obj = default( object );
    }
    else
    {
    obj = Convert.ChangeType( obj, type );
    }
    return obj;
    }

    }
    /// 以上代码来自园子里另一个仁兄的博客,前段时间放了首页,被吾等收藏之
    /// 利用反射自己写的一个ModelHelper类
    /// http://www.cnblogs.com/inday/archive/2008/12/23/1360495.html

    上边这个应用场景是
    需要多次获取PropertyInfo,继而使用其SetValue方法,按照我的理解,
    SetValue仍然可以使用您的方式来操作,可是我担心的是频繁获取PropertyInfo是否依然会影响性能,[PS:我不会纠集于几个时钟周期那样而无法解脱,只是在学习时我想应该学到正确的方法],感谢您的文章,期待您的回复!

  85. 老赵
    admin
    链接

    老赵 2009-01-12 10:45:00

    嗯,取PropertyInfo虽然不会影响太大性能,但是相比缓存起来的确也略有消耗。

  86. 路人Z[未注册用户]
    *.*.*.*
    链接

    路人Z[未注册用户] 2009-03-24 13:09:00

    太麻烦了,反射方法调用根本不需要用emit和lambda只要用Deleaget就能达到高效率:
    Func f = Delegate.CreateDelegate(typeof(Func), MethodInfo);
    f(...);

  87. 老赵
    admin
    链接

    老赵 2009-03-24 13:15:00

    @路人Z
    您能否仔细看一下文章内容和它解决的问题再来发表评论呢?

  88. kgame[未注册用户]
    *.*.*.*
    链接

    kgame[未注册用户] 2009-07-26 13:23:00

    寫成擴充方法也不錯
    public static Func<object, object[], object> GetExecuteDelegate(this MethodInfo methodInfo);

  89. 老赵
    admin
    链接

    老赵 2009-07-26 15:36:00

    @kgame
    扩展方法必须放在额外的static类中,有点多此一举。

  90. kgame[未注册用户]
    *.*.*.*
    链接

    kgame[未注册用户] 2009-07-26 19:15:00

    把DynamicMethodExecutor類別標上static就好
    不也差不多嗎

  91. 老赵
    admin
    链接

    老赵 2009-07-26 19:19:00

    @kgame
    兄弟不妨再仔细看看文章和代码吧,最好再试验一下。

  92. kgame[未注册用户]
    *.*.*.*
    链接

    kgame[未注册用户] 2009-07-28 14:05:00

    少一次Execute方法调用
    不是很好嗎

  93. 老赵
    admin
    链接

    老赵 2009-07-28 14:09:00

    @kgame
    兄弟,你把整个代码完整写一遍就知道问题在哪里了。搞技术的要多实践,先实践再动嘴。
    希望下次你能给我一个可以执行的,功能相同的DynamicMethodExecutor类,并给出使用示例。
    不要再是一句话评论了,本来你可以自己体会到的东西,我也没法解释给你听,是不是?

  94. kgame[未注册用户]
    *.*.*.*
    链接

    kgame[未注册用户] 2009-07-28 21:24:00

    public static class DynamicMethodExecutor
    {
    public static Func<object, object[], object> GetExecuteDelegate(this MethodInfo methodInfo)
    {...}
    }
    這樣寫成擴充方法有何不妥
    使用時
    MethodInfo mi = typeof(DynamicMethodExecutor).GetMethod("GetExecuteDelegate");
    var F = mi.GetExecuteDelegate();
    var FF = F(null, new object[] { mi }) as Func<object, object[], object>;
    var FFF = FF(null, new object[] { mi }) as Func<object, object[], object>;
    運作非常良好

  95. 老赵
    admin
    链接

    老赵 2009-07-28 21:50:00

    @kgame
    没有问题,因为你改变了接口。
    你看一下我的FastReflectionLib可以知道我的目的了。
    谢谢提醒。

  96. kgame[未注册用户]
    *.*.*.*
    链接

    kgame[未注册用户] 2009-07-28 22:00:00

    請問FastReflectionLib貼在哪裡
    好像不是在這篇文章中

  97. 老赵
    admin
    链接

    老赵 2009-07-28 22:31:00

  98. 北京[未注册用户]
    *.*.*.*
    链接

    北京[未注册用户] 2009-09-04 17:21:00

    关于Expression Tree,能不能推荐几篇

  99. 老赵
    admin
    链接

    老赵 2009-09-04 18:28:00

    @北京
    装配脑袋写过几篇不错的。

  100. qmxle
    *.*.*.*
    链接

    qmxle 2009-10-01 23:21:00

    老赵:你好!请帮忙解决一下这个问题:
    我看到你文章中提到的“Code Project的文章《A General Fast Method Invoker》”,我今天下载了他的代码,发现如果类型是"struct"而不是class时,结果有错误。测试的代码如下:

        class Program
        {
            static void Main(string[] args)
            {
                Type t = typeof(Person);
                MethodInfo methodInfo = t.GetMethod("Say");
                Person person = new Person(30);
                methodInfo.Invoke(person, null);
                FastInvokeHandler fastInvoker = GetMethodInvoker(methodInfo);
                fastInvoker(person, null);
                Console.ReadLine();
            }
        }
    
        public struct Person
        {
            private readonly int _age;
    
            public Person(int age)
            {
                _age = age;
            }
    
            public void Say()
            {
                Console.WriteLine("My age is {0}.", _age);
            }
        }
    
    


    结果却是:
    My age is 30.
    My age is 1854584.

    我猜这是对于struct没有正确的box/unbox造成的?由于不懂IL代码,只能做此猜测。
    我已经在CodeProject发表了回复了,只是由于英文太差,恐怕作者不能理解,所以麻烦您给提一下?
    谢谢!

  101. 老菜
    123.133.161.*
    链接

    老菜 2010-05-14 23:50:50

    先记号再看。
    这个博客是否有这样的功能:查找我所有回复,进而找到我所有回复过的文章?

  102. 链接

    tsorgy 2010-08-12 00:14:48

    @老赵

    放假回家来后到处逛网,发现了老赵的blog。这段时间一直在读老赵之前写的系列文章,我能从中学习到很多,希望老赵继续像这样与大家分享自己的心得体会,在这里谢过了。

    另外请教个问题,我有个这样的场景,有个方法,根据泛型类型中的所有属性从DbDataReader中读取对应字段填充到对象中。

    private TEntity Create<TEntity>(DbDataReader reader)
        where TEntity : new ()
    {
        //创建实例
        TEntity obj = new TEntity();
        Type type = obj.GetType();
    
        CodeTimer.Initialize();
        CodeTimer.Time("DynamicExecutor - " + type.Name, 1, () =>
        {
            // 遍历所有的公有属性
            foreach (PropertyInfo pInfo in type.GetProperties())
            {
                MethodInfo method = pInfo.GetSetMethod(false);
                if (method != null)
                {
                    DynamicMethodExecutor executor = new DynamicMethodExecutor(method);
                    try
                    {
                        object value = reader[pInfo.Name];
                        //method.Invoke(curObj, new object[] { reader[pInfo.Name] });
                        executor.Execute(curObj, new object[] { reader[pInfo.Name] });
                    }
                    catch (Exception e)
                    {
                        throw e;
                    }
                }
            }
        });
        return curObj;
    }
    

    如上,但是实际测试中(用这个方法创建3个不同类型对象)我发现似乎使用DynamicMethodExecutor消耗的时间(分别是5ms、2ms、2ms)比注释掉Dynamic...与executor单纯用Invoke消耗的时间(都是0ms)要多。(这是我刷新了页面多次后Debug打印出来的值)

    我想请教一下在需要反复构造DynamicMethodExecutor的时候是不是用Invoke要节省一点呢

  103. loogn
    123.14.82.*
    链接

    loogn 2010-09-19 16:15:18

    如果按"追求编程之美"的观点看,有个地方的代码不相付哦!

    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(instance, parameters);
            return null;
        };
    }
    

    返回值用Action

    public Action<object, object[]> GetVoidDelegate()
    {
        Expression<Action<object, object[]>> exp = (InstanceData, parameters) => ((Program)InstanceData).Call(parameters[0], parameters[1], parameters[2]);
        Action<object, object[]> action = exp.Compile();
        return action;
    }
    
  104. 老赵
    admin
    链接

    老赵 2010-09-19 21:46:57

    @loogn

    很显然这是为了保持接口的统一么……

  105. 链接

    世界 2010-10-09 17:20:26

    是不是可以完全替代反射的功能了凡是invoke的地方都可以用这个替换了?

  106. 链接

    hhshuai 2010-10-26 14:05:49

    像这样的反射:IWork为一个接口,

    ITypeProcess process = (IWork)Assembly.Load("RealSailing.BizControls").CreateInstance(
        String.Format("RealSailing.UI.Utils.{0}Process", type),
        false,
        BindingFlags.Default,
        null,
        obj,
        new CultureInfo("zh-CN", false),
        new object[] { });    
    

    状态反射得出来的实例process,调用其中的方法process.DoWork(),为什么性能比直接调用的还要高?这是怎么一回事,谢谢老赵!

    我一般是用下面的第一种调用方式,有什么问题吗?测试代码及结果(Release):

    Stopwatch watch3 = new Stopwatch();
    IWork instance = (IWork)assembly.CreateInstance(
        names[0],
        false,
        BindingFlags.Default,
        null,
        new object[] { },
        new CultureInfo("zh-CN", false),
        new object[] { });
    
    watch3.Start();
    for (int i = 0; i < 100000; i++)
    {
        instance.DoWork(new string[] { });
    }
    watch3.Stop();
    Console.WriteLine(watch3.Elapsed + " (reflection executor)");
    
    Stopwatch watch4 = new Stopwatch();
    FetchMsg msg = new FetchMsg();
    watch4.Start();
    for (int i = 0; i < 100000; i++)
    {
        msg.DoWork(new string[] { });
    }
    watch4.Stop();
    Console.WriteLine(watch4.Elapsed + " (Directly executor)");
    

    结果:

    00:00:00.0018585 (reflection executor)
    00:00:00.0022870 (directory executor)
    
  107. asdasdasdasd
    218.29.176.*
    链接

    asdasdasdasd 2011-09-14 13:34:58

    文章的实际使用逻辑在哪里,既然知道了program 又知道了args ,为什么不直接调用? 反射的使用是不得已才反射,而且统计的时候,没统计methodinfo得来的时间

  108. 老赵
    admin
    链接

    老赵 2011-09-14 17:11:12

    @asdasdasdasd

    现在假设的就是必须用到使用反射,做测试的时候你还想要什么?统计的时候完全没必要算上得到MethodInfo的时间,因为本来就是一次性的。

  109. fenrir
    110.87.3.*
    链接

    fenrir 2012-01-20 04:07:52

    赵老师你好,我刚才测试了一下通过Lambda表达式调用的性能要比反射调用高的话必须达到大约一万次以上才有一定的提高,这样的话不是在实际应用中需要缓存这些委托才行?再建立一套缓存的话是否会得不偿失呢?

  110. 老赵
    admin
    链接

    老赵 2012-01-21 16:49:37

    @fenrir

    没懂你什么意思,总之需要的话自己试验咯。

  111. Juvy
    112.91.66.*
    链接

    Juvy 2012-06-23 09:58:18

    对于一个空方法的执行我觉得没什么实际的意义,我觉得至少得在方法里面加几条语句,比如新建一个Form,然后为加上几个控件试试看,下面是我执行100w次的测试结果:

    Program p = new Program();
    
    p.Call(...):
             Time Elapsed:   8,294ms
             Gen 0:          356
             Gen 1:          94
             Gen 2:          18
    
    typeof(Program).GetMethodInfo("Call").Invoke(...):
             Time Elapsed:   12,019ms
             Gen 0:          361
             Gen 1:          138
             Gen 2:          43
    
    ()={return p.Call(...);
             Time Elapsed:   7,749ms
             Gen 0:          353
             Gen 1:          110
             Gen 2:          23
    
    DenamicMethod:
             Time Elapsed:   7,695ms
             Gen 0:          355
             Gen 1:          103
             Gen 2:          21
    

    下面是我的Call方法:

    public object Call(object obj1, object obj2)
    {
        Form frm = new Form();
        UserControl uc = new UserControl();
        uc.Dock = DockStyle.Fill;
        frm.Controls.Add(uc);
        return null;
    }
    

    从上面的测试结果看来,执行100w次的话,好像差距不是很明显:速度方面只相差了不到1倍;垃圾回收方面也差不多差了1倍。

  112. 老赵
    admin
    链接

    老赵 2012-06-23 15:08:44

    @Juvy

    空方法才是真正有针对性的比较,你这个时间完全花在其他代码上了。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我