程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 玩轉動態編譯(三) 提高性能,拋棄反射

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

編輯:關於C#

通過之前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的時候,說不定這些真的能幫上你,不是嗎?

查看本欄目

再來一點廢話

我給我自己定 下了一個計劃,工作日至少每天一篇博客(周六周日就陪陪老婆孩子了),即使每天都寫到2點多,為的除 了鍛煉自己以外,也是想找一些志同道合的朋友們一起討論心得,分享經驗

所以大家千萬不要吝啬 自己的評論哦!我很樂意回復的。

下一篇其實我很想寫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&q uot;,"Birthday":"1986-10-29 1
8:00:00","Sex":"Male","IsDeleted":false,"LoginHisto ry":["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&q uot;,"Birthday":"1986-10-29 1
8:00:00","Sex":"Male","IsDeleted":false,"LoginHisto ry":["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}}

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved