反射是否真的會讓你的程式效能降低?

馬非碼的部落格發表於2014-12-06

好了,正題開始。早兩天寫了《從把三千行程式碼重構成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%總好過你請個牛逼的工程師來幫你做這種極限的優化,有一句話我覺得很對“工程師比伺服器要昂貴的多”。如果你還非得跟我較勁,那麼,沒辦法了,你程式對效能的要求已經超出了本文討論的範疇,如果你真有這種需求了,我覺得你也沒有必要看我這篇文章了,因為你已經足夠牛逼到對系統語言都有深入瞭解了。

大多時候,我們會把程式的效能歸結於程式語言,或者使用了反射等技術,而甚少去關心自己的程式碼,這種心態會導致你技術的發展越來越緩慢,因為你已經失去了求知的慾望,以及一顆追求技術進步的心。請你記住,更多的時候,影響我們程式效能的,是你程式設計的思想,你對待編碼的態度!

總結

好吧,說了這麼多,估計很多人直接就拖到文章末尾然後因為文章碼了這麼多字而默默點了個贊,那麼,我在最後給大家奉獻一下本文的精華:

  1. 反射大概比直接呼叫慢50~100倍,但是需要你在執行100萬遍的時候才會有所感覺
  2. 判斷一個函式的效能,你需要把這個函式執行100萬遍甚至1000萬遍
  3. 如果你只是偶爾呼叫一下反射,請忘記反射帶來的效能影響
  4. 如果你需要大量呼叫反射,請考慮快取。
  5. 你的程式設計的思想才是限制你程式效能的最主要的因素

相關文章