C 之 反射效能優化2

2021-09-09 02:07:24 字數 4094 閱讀 2018

問題回顧

在上篇部落格中,我介紹了優化反射的第乙個步驟:用委託呼叫代替直接反射呼叫。

然而,那只是反射優化過程的開始,因為新的問題出現了:如何儲存大量的委託?

如果我們將委託儲存在字典集合中,會發現這種設計會浪費較多的執行時間,因為這種設計會引發三個新問題:

1. **的執行路徑變長了。

2. 字典查詢是有成本開銷的。

3. 字典集合的併發讀寫需要鎖定,會影響併發性。

再來回顧一下上次的測試結果吧:

雖然通用介面isetvalue將反射效能優化了37倍,但是最終的fastsetvalue將這個數字減少到還不到7倍(在clr4中還不到5倍)。

難道您不覺得遺憾嗎?

再看看直接呼叫與反射呼叫的對比,它們的速度相差了上千倍!

既然委託最後引出了三個難以解決的問題,導致優化後速度比直接呼叫差距太遠,那我們能不能不使用委託呢?

委託呼叫並不是優化反射的唯一方案,我們還有其它方法,

之所以委託呼叫能成為常見的優化方案是因為它比較簡單。

假如我需要用客戶端提交的資料來填充某個資料物件,考慮到**的通用性,我會用反射寫成這樣:

/// /// 從httprequest載入obj所需的資料

}

如果我事先知道要載入已知的資料型別,**會寫成這樣:

顯然,第二段**執行效率更快(儘管第一段**呼叫fastsetvalue優化了速度)。

大家都知道反射效能較差,直接呼叫效能最好,那麼能不能在執行時不使用反射呢?

的確,使用反射是因為我們事先不知道要處理哪些型別的物件,因此不得不用反射, 另外,反射的**也更通用,寫乙個方法可以載入所有的資料型別,可認為是一勞永逸的方法。 不過,就算我們事先不知道要處理哪些物件型別,但是只要使用反射,我們完全可以知道任何乙個型別包含哪些資料成員, 還能知道這些資料成員的資料型別,這一點不用懷疑吧? 既然我們用反射可以知道所有的型別定義資訊,我們是否可以參照**生成器的思路去生成**呢? 我們可以參照前面第二段**,為【需要處理的型別】生成直接呼叫的**,這樣不就徹底解決了反射效能問題了嗎? 生成**的過程,其實也就是個字串的拼接過程,難度並不大,只是比較複雜而已。

如果前面的答案都是肯定的,那麼現在只有乙個問題了:我們能在執行時執行拼接生成的字串**嗎?

答案也是肯定的:能!

回憶一下我們編寫的aspx頁面,它們並不是c#**,它們本質上就是乙個文字檔案, 我們可以寫入一些html標籤,還有些標籤上加了 runat="server" 屬性, 我們還可以在頁面中插入一些c#**片段,儘管它們不是我們編譯後的dll檔案,然而它們就是執行起來了! 要知道asp.net不是asp,asp是解釋性的指令碼語言,而asp.net是以編譯方式執行的, 所以,每個aspx頁面檔案最後都是執行編譯後的結果。

假設我有下面一段文字(文字的內容是一段c#**):

using system;

using system.collections.generic;

using system.text;

using system.reflection;

namespace optimizereflection

public string name;

public int add(int a, int b)

}public class 使用者手冊

; b=; c=; d=; s=", a, b, c, d, s);}}

}

您可以把上面這段文字想像成前面第二個版本的loaddatafromhttprequest方法,如果我們在執行時使用反射也能生成那樣的**, 現在就差把它編譯成程式集了。下面的**演示了如何將一段文字編譯成程式集的過程:

string code = null;

// 1. 生成要編譯的**。(示例為了簡單直接從程式集內的資源中讀取)

stream stram = typeof(codedom).assembly

.getmanifestresourcestream("testoptimizereflection.使用者手冊.txt");

using( streamreader sr = new streamreader(stram) )

// 2. 設定編譯引數,主要是指定將要引用哪些程式集

compilerparameters cp = new compilerparameters();

cp.generateexecutable = false;

cp.generateinmemory = true;

cp.referencedassemblies.add("system.dll");

cp.referencedassemblies.add("optimizereflection.dll");

// 3. 獲取編譯器並編譯**

// 由於我的**使用了【自動屬性】特性,所以需要 c# .3.5版本的編譯器。

// 獲取與clr匹配版本的c#編譯器可以這樣寫:codedomprovider.createprovider("csharp")

dictionarydict = new dictionary();

dict["compilerversion"] = "v3.5";

dict["warnaserror"] = "false";

csharpcodeprovider csprovider = new csharpcodeprovider(dict);

compilerresults cr = csprovider.compileassemblyfromsource(cp, code);

// 4. 檢查有沒有編譯錯誤

if( cr.errors != null && cr.errors.haserrors )

// 5. 獲取編譯結果,它是編譯後的程式集

assembly asm = cr.compiledassembly;

整個過程分為5個步驟,它們已用注釋標識出來了,這裡不再重複了。

前面的**把一段文字字串編譯成了程式集,現在還有最後乙個問題:如何呼叫編譯結果?

答案:有二種方法,

1. 直接呼叫方法。

2. 例項化程式集中的型別,以介面方式呼叫方法。

其實這二種方法都需要使用反射,用反射定位到要呼叫的型別和方法。

第一種方法要求在生成**時,生成的類名和方法名是明確的,在呼叫方法時,我們有二個選擇:

1. 用反射的方式呼叫(這裡只是一次反射)。

2. 為方法生成委託(用上篇部落格介紹的方法),然後基於委託呼叫。

第二種方法要求在生成**時,首先要定義乙個介面,保證生成的**能實現指定的介面,

然而用反射找到要呼叫的型別名稱,用反射或者委託呼叫構造方法建立型別例項,最後基於介面去呼叫。

我們熟悉的aspx頁面就是採用了這種方式來實現的。

這二種方法也可以這樣區分:

1. 如果生成的方法是靜態方法,應該選擇第一種方法。

2. 如果生成的方法是例項方法,那麼選擇第二種方法是合理的。

對於前面的示例,我採用了第一種方法了,因為類名和方法名稱都是事先確定的而且實現起來比較簡單。

// 6. 找到目標方法,並呼叫

type t = asm.gettype("optimizereflection.使用者手冊");

methodinfo method = t.getmethod("main");

method.invoke(null, null);

能不能不使用委託? 如何用好codedom?

在這篇部落格中我不知道把它們安排在**較為合適,算了,還是把答案留給下篇部落格吧。

完成埠之效能優化 2

前面有朋友對本系列文章的題目提出質疑,說 這恐怕不能算是效能優化吧?我要指出的是,本系列文章中提到的優化並不僅僅是某段具體的 優化,當然這種東西肯定會有,但優化絕不僅僅是這些方面,我這裡提到的優化還包括更多的關於模型架構方面的考量。上次我提到,在模型裡,引入 池 的概念可以有效改善伺服器效率。對於完...

完成埠之效能優化 2

前面有朋友對本系列文章的題目提出質疑,說 這恐怕不能算是效能優化吧?我要指出的是,本系列文章中提到的優化並不僅僅是某段具體的 優化,當然這種東西肯定會有,但優化絕不僅僅是這些方面,我這裡提到的優化還包括更多的關於模型架構方面的考量。上次我提到,在模型裡,引入 池 的概念可以有效改善伺服器效率。對於完...

優化反射之開始

繼上篇上篇的用emit 代替直白反射或者是目的是優化反射,初步窺探了一下 emit概貌,現在開始慢慢切入主題 場景如下 1.去資料庫更新乙個類,如果不是用的 ef等orm 元件需要自己手動去修改乙個類的改動字段,然後插入資料庫。現在給出反射方法,demo如下 public static void r...