優化反射效能的總結(中)

2021-07-04 18:19:28 字數 4208 閱讀 3058

閱讀目錄

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

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

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

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")

dictionary

<

string, string

> dict =

new

dictionary

<

string, string

>();

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);

C 之 反射效能優化2

問題回顧 在上篇部落格中,我介紹了優化反射的第乙個步驟 用委託呼叫代替直接反射呼叫。然而,那只是反射優化過程的開始,因為新的問題出現了 如何儲存大量的委託?如果我們將委託儲存在字典集合中,會發現這種設計會浪費較多的執行時間,因為這種設計會引發三個新問題 1.的執行路徑變長了。2.字典查詢是有成本開銷...

效能優化總結

儘量減少布局的層級,有選擇的使用功能較為複雜,效能較低的viewgroup,如 relativelayout,但推薦使用relativilayout來替換多重巢狀的linearlayout,減少布局層級。推薦使用,和viewstub來布局。避免在view的ondraw中進行大量的操作。主要體現於兩個...

效能優化總結

最近在查效能優化,總結了以下幾條 第一 在接受訊息 socket地方 打上每條訊息的消耗 這個能快速定位到 哪些函式消耗的比較高 第二 在遊戲主迴圈中,打上沒幀的消耗 如果消耗是穩定網 的 說明一定有物件建立了沒有刪掉 並且這個物件還在 主 迴圈中 可以檢查一下 註冊的各地地方的定時器 陣列是否有物...