通過之前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}}
所以還是那句話,期待吧~