zoukankan      html  css  js  c++  java
  • 玩转动态编译:三、提高性能,抛弃反射

    通过之前2篇文章的介绍,大家一定发现了,动态编译后的对象只能通过反射调用,但是反射往往是一个程序性能的瓶颈,这个真的无法突破么?答案当然是否定的,接下来就我就来说说怎么才能,挖掘动态编译的潜力。

    • 一点废话

    我刚来博客园才1星期左右,昨天才弄懂怎么发表到首页,先说声抱歉了,昨天的文章有几个地方贴的源码居然少了几个字符,有点莫名其妙,也难怪有人不能运行了,虽然是小错误,但是如果认真检查的话也是可以避免的,这是我的失误。

    还有一点,这个《玩转动态编译》是一个系列的,虽然没有大纲,不知道会写到几,但是内容一定是循序渐进的,所以如果你看到了不合理的地方,请不要惊讶,可能我只是为了更好理解,也许下一篇就会把这个地方重构的。

    回复上一篇中的博友 飘的移

    引用我只想:说这个效率实在太慢了,楼主什么时候能做到接近FastJson或者Newton.Json的速度就牛叉了

    FastJson是Java的,我测试不了,但就Newtonsoft.Json的效率来说超过他还是可以的,所以在这个系列没有over之前耐心期待吧。。。。(这个算广告吗)

    • 书归正传,话转正题

    通过之前2篇文章的介绍,大家一定发现了,动态编译后的对象只能通过反射调用,但是反射往往是一个程序性能的瓶颈,这个真的无法突破么?答案当然是否定的。

    那怎么才能抛弃反射呢?

    仔细看之前的《玩转动态编译》大家可以发现,之前2个栗子编译的都是静态方法。
    回到昨天的栗子中,被静态编译的User解析类

    using blqw;
    using System;
    using System.Collections;
    using System.Text;
    
    public class _336090f4e7724d2585b07e79210decb4
    {
        public static string a(User obj)
        {
            return new StringBuilder().Append("{"UID":")
                .Append(Json.Converter2.FromGuid((System.Guid)obj.UID))
                .Append(","Name":")
                .Append(Json.Converter2.FromString((System.String)obj.Name))
                .Append(","Birthday":")
                .Append(Json.Converter2.FromDateTime((System.DateTime)obj.Birthday))
                .Append(","Sex":")
                .Append(Json.Converter2.FromEnum((Enum)obj.Sex))
                .Append(","IsDeleted":")
                .Append(Json.Converter2.FromBoolean((System.Boolean)obj.IsDeleted))
                .Append(","LoginHistory":")
                .Append(Json.Converter2.FromArray(((IEnumerable)obj.LoginHistory).GetEnumerator()))
                .Append(","Info":")
                .Append(Json.ToJson_2(obj.Info))
                .Append("}").ToString();
        }
    } 

    ps:其实回车都是我刚刚加上去的,难道我会乱说?

    编译静态的方法,只是为了在反射调用Invoke的时候不要传入实例对象,就像这样

    var code = CreateCode(type);//获得代码
    var ass = DynamicCompile_1.CompileAssembly(code, typeof(Json), typeof(StringBuilder), typeof(IDictionary), typeof(Enum), typeof(IEnumerable), typeof(IEnumerator));//编译
    var met = ass.GetTypes()[0].GetMethods()[0];//反射唯一的一个对象中的唯一的一个方法
    return (string)met.Invoke(null, new object[] { user });//执行方法,等到返回值

    程序中,我们可以缓存最后的met对象,可以防止反复的编译。不过就算是这样,每次调用met对象的时候依然是反射调用(Invoke)

    是静态方法的话就意味着必须要使用反射,静态只能通过 类名.方法名 来调用,而动态编译的类名是在程序运行时决定的。。。。

    • 思考?

    那么是否意味着实例方法就可以呢?

    把上面的动态代码改一下

    using blqw;
    using System;
    using System.Collections;
    using System.Text;
    
    public class _336090f4e7724d2585b07e79210decb4 : blqw.IGetString
    {
        public string GetString(object o)
        {
            User obj = (User)o;
            return new StringBuilder().Append("{"UID":")
                .Append(Json.Converter2.FromGuid((System.Guid)obj.UID))
                .Append(","Name":")
                .Append(Json.Converter2.FromString((System.String)obj.Name))
                .Append(","Birthday":")
                .Append(Json.Converter2.FromDateTime((System.DateTime)obj.Birthday))
                .Append(","Sex":")
                .Append(Json.Converter2.FromEnum((System.Enum)obj.Sex))
                .Append(","IsDeleted":")
                .Append(Json.Converter2.FromBoolean((System.Boolean)obj.IsDeleted))
                .Append(","LoginHistory":")
                .Append(Json.Converter2.FromArray(((IEnumerable)obj.LoginHistory).GetEnumerator()))
                .Append(","Info":")
                .Append(Json.ToJson_2(obj.Info))
                .Append("}").ToString();
        }
    }

    看有那些地方改变?

    1,实现了IGetString的接口

    namespace blqw
    {
        public interface IGetString
        {
            string GetString(object obj);
        }
    }
    IGetString 接口

    2,方法签名去掉了static变为实例方法

    3,接受参数从User变为Object

    • 抛弃反射

    这三处变化为我们带来的好处是显而易见的,现在我们可以这样调用方法:

    var code = CreateCode(type);//获得代码
    var ass = DynamicCompile_1.CompileAssembly(code, typeof(Json), typeof(StringBuilder), typeof(IDictionary), typeof(Enum), typeof(IEnumerable), typeof(IEnumerator));//编译
    var get = (IGetString)ass.GetTypes()[0].GetConstructor(Type.EmptyTypes).Invoke(null);//反射唯一的一个类,并实例化他,同时将他转换为一个接口实例
    return get.GetString(user);//直接调用接口方法

     注意最后一行代码,这里并没有使用反射。这将意味着我可以缓存这个IGetString实例,之后的程序中再次调用也仅仅是调用一个方法,不会再用到反射了!

    • 性能测试

    让我继续用一个栗子给大家展示2种调用方法之间的性能差异

    public class Program : IGetString
    {
        public static string A(object obj)
        {
            return obj.ToString();
        }
    
        public string GetString(object obj)
        {
            return obj.ToString();
        }
    
        static void Main(string[] args)
        {
            var user = GetUser();           //准备一个参数
            Type type = typeof(Program);    //准备一个Type对象用于反射
            for (int j = 0; j < 10; j++)//整体测试10次
            {
    
                Stopwatch sw = new Stopwatch();
                sw.Start();//这里开始计时,将第一次反射为缓存的时间也计算在内
                var met = type.GetMethod("A");  //得到静态的A方法
                for (int i = 0; i < 1000000; i++)//因为楼主的笔记本性能比较好,所以需要大量循环才能看出差异
                {
                    met.Invoke(null, new object[] { user });
                }
                sw.Stop();
                Console.Write(sw.ElapsedMilliseconds + "ms");
                Console.Write(" | ");
    
                sw.Restart();
                var get = (IGetString)type.GetConstructor(Type.EmptyTypes).Invoke(null);
                for (int i = 0; i < 1000000; i++)
                {
                    get.GetString(user);
                }
                sw.Stop();
                Console.WriteLine(sw.ElapsedMilliseconds + "ms");
            }
        }

    测试结果

    333ms | 25ms
    330ms | 24ms
    326ms | 24ms
    320ms | 24ms
    320ms | 23ms
    328ms | 24ms
    326ms | 25ms
    327ms | 24ms
    330ms | 25ms
    328ms | 24ms
    请按任意键继续. . .

    虽然差距值依然很小,只有300ms,但是相对倍率却达到了10倍以上!这意味着运行上面的方法一次,下面的方法就可以跑10次!

    我想依然有人会说,100W次才不到1/3秒,有什么意义?

    但我想说的是,差距不就是这样一点一滴积累起来的吗?等有一天你有机会接触每天上十万,上百万的PV的时候,说不定这些真的能帮上你,不是吗?

    • 再来一点废话

    大家千万不要吝啬自己的评论哦!我很乐意回复的。

    下一篇其实我很想写JsonConverter的优化,但是毕竟《玩转动态编译》这个连载还没有结束就开始一段新的恋情是十分不道德的劈腿行为,所以下一篇的真实情况就是会继续动态编译类DynamicCompile,直到彻底完成它。

    不过对Json有兴趣的也可以mark下,动态编译的连载结束后就会完成Json的优化,按照现在已完成的代码来看,性能已经可以保证比Newtonsoft.Json.Net35.dll更快了。

    剧透一下结果(这个还不是最终的,我还有一个地方正在优化,估计会有10%左右的性能提升)

    纯反射 每次10000 共10次
    168ms | 153ms | 152ms | 152ms | 154ms | 157ms | 157ms | 158ms | 158ms | 153ms |
    
    动态编译 每次10000 共10次
    222ms | 116ms | 118ms | 117ms | 113ms | 109ms | 105ms | 106ms | 105ms | 107ms |
    
    Newtonsoft.Json 每次10000 共10次
    359ms | 177ms | 192ms | 182ms | 188ms | 189ms | 189ms | 189ms | 187ms | 189ms |
    
    ====纯反射====
    {"UID":"1e10fe905f7d41d0bda2210abcb12349","Name":"blqw","Birthday":"1986-10-29 1
    8:00:00","Sex":"Male","IsDeleted":false,"LoginHistory":["2013-08-09 08:00:00","2
    013-08-09 10:10:10","2013-08-09 12:33:56","2013-08-09 17:25:18","2013-08-09 23:0
    6:59"],"Info":{"Address":"广东省广州市","Phone":{"手机":"18688888888","电话":"82
    580000","短号":"10086","QQ":"21979018"},"ZipCode":510000}}
    
    ====动态编译====
    {"UID":"1e10fe905f7d41d0bda2210abcb12349","Name":"blqw","Birthday":"1986-10-29 1
    8:00:00","Sex":"Male","IsDeleted":false,"LoginHistory":["2013-08-09 08:00:00","2
    013-08-09 10:10:10","2013-08-09 12:33:56","2013-08-09 17:25:18","2013-08-09 23:0
    6:59"],"Info":{"Address":"广东省广州市","Phone":{"手机":"18688888888","电话":"82
    580000","短号":"10086","QQ":"21979018"},"ZipCode":510000}}
    
    ====Newtonsoft.Json====
    {"UID":"1e10fe90-5f7d-41d0-bda2-210abcb12349","Name":"blqw","Birthday":"/Date(5
    30964000000+0800)/","Sex":0,"IsDeleted":false,"LoginHistory":["/Date(137600640
    0000+0800)/","/Date(1376014210000+0800)/","/Date(1376022836000+0800)/","/D
    ate(1376040318000+0800)/","/Date(1376060819000+0800)/"],"Info":{"Address":"广
    东省广州市","Phone":{"手机":"18688888888","电话":"82580000","短号":"10086","QQ":
    "21979018"},"ZipCode":510000}}
    
    View Code

    所以还是那句话,期待吧~

    我写的文章,除了纯代码,其他的都是想表达一种思想,一种解决方案.希望各位看官不要局限于文章中的现成的代码,要多关注整个文章的主题思路,谢谢!
    我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
    qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐
  • 相关阅读:
    bzoj 1761: [Baltic2009]beetle 区间dp
    NOI冲刺计划
    bzoj 2107: Spoj2832 Find The Determinant III 辗转相除法
    bzoj 2482: [Spoj GSS2] Can you answer these queries II 线段树
    bzoj 1209: [HNOI2004]最佳包裹 三维凸包
    SCOI2015题解 && 考试小结
    bzoj 2806: [Ctsc2012]Cheat 后缀自动机DP
    考场上应该想到。。。。
    spoj LCS 后缀自动机
    BZOJ 1639: [Usaco2007 Mar]Monthly Expense 月度开支
  • 原文地址:https://www.cnblogs.com/blqw/p/DynamicCompile3.html
Copyright © 2011-2022 走看看