淺談.NET反射機(jī)制的性能優(yōu)化
在進(jìn)入解釋型模版引擎的探討之前,我決定先分享一下這篇博客。因?yàn)樵诮忉屝鸵胬飳?huì)引入反射的概念來(lái)實(shí)現(xiàn)更多、更復(fù)雜的功能??赡艽蠹艺劦椒瓷涿娌考∪舛奸_(kāi)始抽搐了吧!因?yàn)樵谕泄苷Z(yǔ)言里面,最臭名昭著的就是反射!它的性能實(shí)在是太低了,甚至在很多時(shí)候讓我們無(wú)法忍受。不過(guò)不用那么糾結(jié)了,老陳今天就來(lái)分享一下如何來(lái)優(yōu)化反射!
概述
本文涉及到的反射優(yōu)化的途徑有如下兩種:
- 通過(guò)Delegate.CreateDelegate()創(chuàng)建委托進(jìn)行優(yōu)化
- 通過(guò).NET4的動(dòng)態(tài)運(yùn)行時(shí)進(jìn)行優(yōu)化
如果您還知道其他更加有效的優(yōu)化途徑,請(qǐng)不吝賜教!
準(zhǔn)備工作
今天我們總計(jì)要對(duì)比五種不同的調(diào)用對(duì)象成員的方式,也算是一種性能測(cè)評(píng)。
在開(kāi)始之前,我們首先定義一個(gè)簡(jiǎn)單的對(duì)象和一個(gè)方法,以供測(cè)試之用:
1 namespace ReflectionOptimization
2 {
3 public sealed class TestObject
4 {
5 public int Add(int a, int b)
6 {
7 // 簡(jiǎn)單演示
8 return a + b;
9 }
10 }
11 }
這個(gè)類非常簡(jiǎn)單,只提供了一個(gè)方法,這個(gè)方法返回兩個(gè)整形的和。接下來(lái)我們看看執(zhí)行時(shí)間測(cè)量的代碼,很簡(jiǎn)單,想必您已經(jīng)駕輕就熟了:
1 private static double _Run(string description, Action<int, int> action, int a, int b)
2 {
3 if (action == null) throw new ArgumentNullException("action");
4
5 // 啟動(dòng)計(jì)時(shí)器
6 var stopwatch = Stopwatch.StartNew();
7
8 // 運(yùn)行要測(cè)量的代碼
9 action(a, b);
10
11 // 終止計(jì)時(shí)
12 stopwatch.Stop();
13
14 // 輸出結(jié)果
15 Console.WriteLine("{0}: {1}", description, stopwatch.Elapsed.TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
16
17 // 返回執(zhí)行時(shí)間
18 return stopwatch.Elapsed.TotalMilliseconds;
19 }
以上測(cè)量時(shí)間的方法返回了執(zhí)行時(shí)間,因?yàn)槲覀円诤竺嬗玫竭@個(gè)值,在執(zhí)行多次之后取個(gè)平均值,以求測(cè)試的公平性、權(quán)威性。
編碼實(shí)現(xiàn)
首先我們來(lái)看看原生反射的實(shí)現(xiàn):
1 var obj = new TestObject();
2 var add = obj.GetType().GetMethod("Add");
3
4 for (var i = 0; i < _TIMES; i++) add.Invoke(obj, new object[] {a, b});
然后我們看看.NET4動(dòng)態(tài)編程的實(shí)現(xiàn):
1 dynamic obj = new TestObject();
2
3 // 有木有發(fā)現(xiàn)這個(gè)代碼超級(jí)簡(jiǎn)單?
4 for (var i = 0; i < _TIMES; i++) obj.Add(a, b);
最后我們看看如何使用委托來(lái)優(yōu)化反射:
1 // 委托
2 public delegate int AddMethod(int a, int b);
3
4 // 實(shí)現(xiàn)
5 var obj = new TestObject();
6 var objType = obj.GetType();
7 var add = objType.GetMethod("Add");
8 var d = (AddMethod)Delegate.CreateDelegate(typeof(AddMethod), obj, add);
9
10 for (var i = 0; i < _TIMES; i++) d(a, b);
上面的代碼看起來(lái)多了幾行,而且還需要自定義一個(gè)委托,寫(xiě)起來(lái)挺麻煩的。因此我們的測(cè)試代碼里面還實(shí)現(xiàn)了另外一種形式,其實(shí)它也是委托:
var d = (Func<TestObject, int, int, int>)Delegate.CreateDelegate(typeof(Func<TestObject, int, int, int>), add);
測(cè)試總結(jié)
我們首先在Debug模式下將整個(gè)測(cè)試代碼運(yùn)行5遍,然后分別記錄平均值,然后再到Release模式下重復(fù)該測(cè)試。
測(cè)試的過(guò)程不再闡述,測(cè)試結(jié)果整理如下:
Debug模式:
| 調(diào)用方式 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 |
|---|---|---|---|---|---|
| Generic Call | 1.022425 | 1.012885 | 0.990775 | 1.020950 | 1.046880 |
| Reflection | 147.489220 | 146.012010 | 142.690080 | 139.189335 | 141.663475 |
| dynamic | 9.645850 | 9.979965 | 9.307235 | 9.532665 | 9.730030 |
| Func | 1.201860 | 1.214800 | 1.170215 | 1.189280 | 1.239485 |
| Delegate | 1.062215 | 1.061635 | 1.067510 | 1.047180 | 1.075190 |
Release模式:
| 調(diào)用方式 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 |
|---|---|---|---|---|---|
| Generic Call | 0.745600 | 0.741365 | 0.722145 | 0.732630 | 0.725645 |
| Reflection | 141.778260 | 142.855410 | 142.346095 | 139.649990 | 138.541285 |
| dynamic | 9.631460 | 10.341850 | 9.284230 | 9.457580 | 9.060470 |
| Func | 0.882100 | 0.852680 | 0.875695 | 0.854655 | 0.831670 |
| Delegate | 0.710280 | 0.722465 | 0.723355 | 0.727175 | 0.693320 |
點(diǎn)評(píng)&結(jié)論:
- 使用委托優(yōu)化反射之后,其性能與直接調(diào)用相差無(wú)幾,保持在同一個(gè)數(shù)量級(jí)之內(nèi),對(duì)性能要求極度苛刻時(shí)推薦此方案;
- 顯式委托(Delegate)和匿名委托(Func)性能差異非常不明顯,但顯式委托的性能還是好一點(diǎn);
- 原生反射比直接調(diào)用慢出了兩個(gè)數(shù)量級(jí),性能差異達(dá)到了200倍之多!
- .NET 4的動(dòng)態(tài)編程語(yǔ)法相當(dāng)簡(jiǎn)潔,其性能只比直接調(diào)用高出一個(gè)數(shù)量級(jí),由于其語(yǔ)法相當(dāng)簡(jiǎn)潔,我們推薦這種做法!
- 原生反射技術(shù)在Debug模式和Release模式下沒(méi)有太大差異,但其他方式有較為明顯的優(yōu)化效果(請(qǐng)思考為什么);
- 雖然我們今天的測(cè)試不能完全意味著反射優(yōu)化之后可以和直接調(diào)用相媲美,但至少可以從某種程度上擊敗那些個(gè)謠言——誰(shuí)說(shuō)反射就一定會(huì)慢(嘻嘻)!
代碼下載:淺談反射優(yōu)化.zip
浙公網(wǎng)安備 33010602011771號(hào)