通過之前2篇文章的介紹,大家一定發現了,動態編譯后的對象只能通過反射調用,但是反射往往是一個程序性能的瓶頸,這個真的無法突破么?答案當然是否定的,接下來就我就來說說怎么才能,挖掘動態編譯的潛力。
- 一點廢話
我剛來博客園才1星期左右,昨天才弄懂怎么發表到首頁,先說聲抱歉了,昨天的文章有幾個地方貼的源碼居然少了幾個字符,有點莫名其妙,也難怪有人不能運行了,雖然是小錯誤,但是如果認真檢查的話也是可以避免的,這是我的失誤。
還有一點,這個《玩轉動態編譯》是一個系列的,雖然沒有大綱,不知道會寫到幾,但是內容一定是循序漸進的,所以如果你看到了不合理的地方,請不要驚訝,可能我只是為了更好理解,也許下一篇就會把這個地方重構的。
回復上一篇中的博友 飄的移
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); } }
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}}
所以還是那句話,期待吧~