早上看到了《【強烈譴責】博客園園友隨意抄襲他人文章並作為自己原創的行為》這篇博文,實在是讓人氣憤,我相信大部分文章作者都是用心去寫的,就這么隨便被抄襲了,還抄襲得這么徹底,實在是讓人無語,而我竟然也上去贊了一下這篇抄襲的文章,實在罪過。在這里希望各位能夠真正尊重一下他人的勞動成果,抄襲不道德,萬萬使不得!
好了,正題開始。早兩天寫了《從把三千行代碼重構成15行代碼談起》這篇文章,看到評論中有一些同學的回復還是在質疑反射的性能,好像程序用上了反射,就像開上了拖拉機似的。本來我覺得這個話題沒有什么好討論的了,網上已經有太多太多的文章在說這個問題,有疑問的大可以到網上找相關的文章來查閱。但是,我想起來我剛編程的時候,也是遇到這種困惑到網上一查找,從各種角度闡述的都有,本質基本都說出來了,但是還是有很多人不理解,我這里就從我的角度再說一遍。
反射肯定比直接調用慢
這個毋庸置疑了,我這篇文章也不是證明反射有多高效的。
現在的快遞哥很火,那我們就舉個快遞的例子。如果快遞員就在你住的小區,那么你報一個地址:xx棟xx號,那么快遞員就可以馬上知道你在哪里,直接就去到你家門口;但是,如果快遞員是第一次來你們這里,他是不是首先得查查百度地圖,看看怎么開車過去,然后到了小區是不是得先問問物管xx棟怎么找,然后,有可能轉在樓下轉了兩個圈才到了你的門前。
我們看上面這個場景,如果快遞員不熟悉你的小區,是不是會慢點,他的時間主要花費在了查找百度地圖,詢問物業管理。OK,反射也是一樣,因為我事先什么都不知道,所以我得花時間查詢一些其他資料,然后我才能找到你。大家有興趣可以查看反射的實現原理,以及MetaData
的相關概念。
反射到底比直接調用慢多少?
好了,我們知道反射肯定慢的,那么是不是反射就不能用了呢?有些人一聽到慢,就非常着急的下結論,反射怎樣怎樣不行,怎樣怎樣不能用。但是,同學,反射到底比直接調用慢多少,你造嗎,能給我個實際的數據嗎?很多人其實對性能只有個模糊的概念,而沒有數值支撐。之前我給同事找了一個動態解析表達式的類庫,他覺得不太好用,他很聰明,很快的找到了用DataTale.Compute
可以實現公式的動態解析。我問他,這個方法和我給的類庫性能上有什么區別?他跟我說,這個已經很快了,執行1秒都不到。我一聽,就覺得不對勁,你的思想還停留在秒級,跟我談什么性能?
怎么去判斷一個函數的性能?因為函數的執行太快太快了,你需要一個放慢鏡,這樣才能捕捉到他的速度。怎么做?把一個函數執行一百萬遍或者一千萬遍,你才能真正了解一個函數的性能。也就是,你如果想判斷性能,你就不能還停留在秒級,毫秒級的概念,你必須用另外一個概念替代,才能知道真正的性能。結果我同事把這兩種方法執行了100w遍,確實,我提供的類庫比他的快了8秒。
好了,現在拿我早兩天提供的工廠方法來做測試,其中CodeTimer的實現參考老趙的文章《一個簡單的性能計數器:CodeTimer》:
測試方法如下:
[Test]
public void TestReflector()
{
CodeTimer.Time("Direct", 100 * 10000,
() =>
{
var instance = new ConnectionTest();
});
CodeTimer.Time("Reflect", 100 * 10000,
() =>
{
this.GetType().Assembly.CreateInstance("TestPropertyGrid.ConnectionTest");
});
}
測試結果如下:
Direct
Time Elapsed: 25ms
CPU Cycles: 57,582,163
Gen 0: 14
Gen 1: 0
Reflect
Time Elapsed: 3,231ms
CPU Cycles: 8,001,720,795
Gen 0: 269
Gen 1: 1
看到沒,我們的放大鏡起作用了,現在我們大概可以下這么一個結論:在執行100萬遍的時候,反射大概把直接調用慢50~100倍。100倍,咋一看,是相差很大的,但是,我前文說了,別着急下結論,你要看看前提條件。自古我們就喜歡斷章取義,比如“以德報怨”這個成語,好像古人說讓我們遇到不好的,你不能怨恨,要更好的對待他人,別人打你左臉一巴掌,你應該把右臉伸過去讓他再打一下。但實際這個成語是怎樣的呢?
或曰:“以德報怨,何如?”
子曰:“何以報德?以直報怨,以德報德”
老孔的意思其實是如果別人對你好,那么你就對他好,要是他招你惹你了,你就干他娘的!你看,傻眼了吧?
有多少情況下需要考慮反射帶來的影響?
我認為這個情況是非常非常少的,絕大多數的我們根本就無需考慮這個。就上我上一篇文章提到的工廠,你程序有多少個實體,有100萬個嗎?如果你只是在彈出窗口的時候new一下,這個百萬分之十秒的影響對你很重要嗎?
另外,有些人講,我要是真有這種需求,要把一個對象new一百萬遍,那不還是慢嗎?這種情況有沒有,有!比如我有100w條記錄,需要取出來,然后通過反射賦值到一個Model
類中。
但是對於這種情況,如果你真是這么想的話,我只能說,你坐辦公室坐久了,腦袋生銹了,該去爬爬山,泡泡妞了。如果你需要對一個對象反射一百萬遍,那么你就應該緩存這個對象了。拿我們上面那個例子來說,如果這個快遞員給小區的人送一百萬遍的快遞還認不得路,每次都還得百度地圖,然后問物業管理,你丫的你還沒把他開掉了,那你腦袋不是秀逗了,要不就是任性的有錢人。
上面代碼如果緩存之后執行一百萬遍,跟直接調用有多大的區別?我這里就不貼代碼了,免得你們直接看結果沒有意思,自己把代碼敲一遍,印象更深刻。
那么,還有沒有更快的辦法,有。比如你的快遞員開始用的是IPHONE4,現在可以考慮給他買個6+。在.net中,提供了Emit的相關方法來讓你更快的反射。這里送你一個通過反射快速給Model賦值的輪子“Dapper”,自己回家造去。
編程中是否應該使用反射?
其實看完上面的文字,我相信你們都有了一個初步的判斷,而我的看法是:絕大多數的情況下你都可以用反射。
如果你覺得是因為反射導致你程序慢的話,那么,請先用放慢鏡好好觀察一下,到底是不是反射的問題。如果你確定是反射的問題,那么你再好好的考慮下是不是你沒有用對反射,是不是像上面那個走了一百萬遍都不認識路的快遞員一樣。最后,如果你覺得性能上還是不夠,那么我建議你升級下硬件吧,把硬件性能上升個3%總好過你請個牛逼的工程師來幫你做這種極限的優化,有一句話我覺得很對“工程師比服務器要昂貴的多”。如果你還非得跟我較勁,那么,沒辦法了,你程序對性能的要求已經超出了本文討論的范疇,如果你真有這種需求了,我覺得你也沒有必要看我這篇文章了,因為你已經足夠牛逼到對系統語言都有深入了解了。
大多時候,我們會把程序的性能歸結於編程語言,或者使用了反射等技術,而甚少去關心自己的代碼,這種心態會導致你技術的發展越來越緩慢,因為你已經失去了求知的欲望,以及一顆追求技術進步的心。請你記住,更多的時候,影響我們程序性能的,是你編程的思想,你對待編碼的態度!
總結
好吧,說了這么多,估計很多人直接就拖到文章末尾然后因為文章碼了這么多字而默默點了個贊,那么,我在最后給大家奉獻一下本文的精華:
- 反射大概比直接調用慢50~100倍,但是需要你在執行100萬遍的時候才會有所感覺
- 判斷一個函數的性能,你需要把這個函數執行100萬遍甚至1000萬遍
- 如果你只是偶爾調用一下反射,請忘記反射帶來的性能影響
- 如果你需要大量調用反射,請考慮緩存。
- 你的編程的思想才是限制你程序性能的最主要的因素
一個人的思想天花板有多高,發展的空間就有多大