玩轉動態編譯:三、提高性能,拋棄反射


通過之前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

所以還是那句話,期待吧~


免責聲明!

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



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