揭秘 ClownFish 比手寫代碼還快的原因


說明:本文的第一版由於反對人數較多(推薦/反對數量是:23 / 17), 我在8月20日刪除了博文內容,只留下一段簡單的內容。


既然分享技術也引來這么多的反對,那我就不分享了。

如果希望知道我的優化方法,請回復留下email地址。


但是讓我萬萬沒有想到的是:到10月17日為止,內容沒有了,推薦數量還翻了一倍。
為了表示對所有點過【推薦】的朋友表示感謝,我決定重寫本文。
在此,尤其要感謝那些 在沒有博文的情況下仍然願意點擊推薦 的朋友,真心謝謝你們的支持。
我的每篇博文后面都會說:我的寫作熱情也離不開您的肯定支持。,所以,是你們重新給了我重寫本文的熱情。

本文的第一版,說了過多的理論內容。 在我閱讀一些人的評論后(來自其它博客),我發現解釋理論是個失敗的寫作方法, 這次重寫,我決定不提任何理論內容,直接用代碼解釋【ClownFish 比手寫代碼還快的原因】。

一般說來,用純ADO.NET的方式從數據庫加載一個實體列表,【應該】會寫成這樣:

private static List<Product> LoadProduct_1(DbDataReader reader)
{
    List<Product> list = new List<Product>();

    while( reader.Read() ) {
        Product p = new Product();
        p.ProductID = (int)reader["ProductID"];
        p.ProductName = reader["ProductName"].ToString();
        p.CategoryID = (int)reader["CategoryID"];
        p.Unit = reader["Unit"].ToString();
        p.UnitPrice = (decimal)reader["UnitPrice"];
        p.Remark = reader["Remark"].ToString();
        p.Quantity = (int)reader["Quantity"];
        list.Add(p);
    }
    return list;
}

然而,這段代碼在性能上,並不是最優秀的,最優秀的代碼應該是這樣的:

private static List<Product> LoadProduct_2(DbDataReader reader)
{
    List<Product> list = new List<Product>();

    while( reader.Read() ) {
        Product p = new Product();
        p.ProductID = reader.GetInt32(0);
        p.ProductName = reader.GetString(1);
        p.CategoryID = reader.GetInt32(2);
        p.Unit = reader.GetString(3);
        p.UnitPrice = reader.GetDecimal(4);
        p.Remark = reader.GetString(5);
        p.Quantity = reader.GetInt32(6);
        list.Add(p);
    }
    return list;
}

在 LoadProduct_2 方法中,直接通過序號去讀取數據,而且使用專用類型的方法,因此,速度是無敵的。

二段代碼貼完了,我想有必要申明一下了:
我這里所說的【手工代碼】,是指在能在項目中使用的手工代碼,因此,LoadProduct_1應該是這類代碼的代表。

LoadProduct_2方法中所使用的小技巧,我想有些人是知道的。
但是,我想【正常人】是不會在項目中寫這樣的代碼的,
因為這樣的代碼很難維護,你可以自己想像一下:
1. 它要求ProductID,ProductName,CategoryID,.....等等字段的輸出順序【一定】是固定的。
2. 所有代碼中出現的字段必須同時輸出。

現實情況是:
1. 實體對象可以從不同的查詢結果中加載,不同的查詢語句,字段的輸出順序不會一樣。
2. 不同的查詢輸出的字段的數量不會一樣,Product對象有時可能加載3個成員,有時可能加載5個成員。

所以,LoadProduct_2的那種數據加載方法,【正常人】是不會在項目中那樣寫的。
也因為這個緣故,我認為手工版本的ADO.NET代碼應該是LoadProduct_1那樣子才對。

自從這篇博客出現后,我發現一直有人認為我的說法【ClownFish比手工代碼要快】不正確,
他們於是就拿出LoadProduct_2那種代碼與ClownFish做比較。
對於這種人,我真是無語了。
我還是那個問題:你在項目中是那樣寫代碼的嗎?
曾經聽到有人說:他的項目中有這樣的代碼,是用代碼生成器生成的。
這個回答實讓我更無語了:代碼生成器生成的代碼是你寫手的嗎??

其實,我何嘗不知道LoadProduct_2的那種方法,
LoadProduct_2中的代碼就是ClownFish的優化目標, ClownFish正是采用這種方式來加載數據的。
或許你會認為很奇怪:ClownFish是一個類庫,怎么能以這種方式加載數據?

答案:沒什么奇怪的,ClownFish內部自帶一個代碼生成器,可以根據實體類型的定義,生成這樣的代碼,
然后在運行時,動態編譯這些代碼,最后直接調用它們。
因此,ClownFish在加載數據時,根本不使用反射,而且使用優化的數據加載方式,所以,性能非常好。

關於ClownFish的編譯模式,需要注意的是,ClownFish默認情況下不開啟用編譯模式。
點擊此處閱讀:讓ClownFish以編譯模式工作

我承認,ClownFish的速度確實要比LoadProduct_2稍微慢一點,
由於,ClownFish是一個類庫,它不知道將要加載什么數據集,它不可能做出任何關於字段順序的假設,
因此,ClownFish在運行時動態生成的代碼要添加一些判斷,處理順序、字段是否存在以及數據庫空值等等問題,
所以,ClownFish不可能達到LoadProduct_2的速度。
另外,請你想一下:LoadProduct_2的代碼有什么意義? 就是證明手工代碼比ClownFish快嗎?

雖然理論上用純ADO.NET的方式,是可以比ClownFish快,然而那種調用方式不適合用在實際項目中。
如果你仍然固執地拿LoadProduct_2來證明手工代碼比ClownFish快,
我還建議你不要活在理論的夢想中,除非你真的想寫出不讓人維護的代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM