在進入解釋型模版引擎的探討之前,我決定先分享一下這篇博客。因為在解釋型引擎里將會引入反射的概念來實現更多、更復雜的功能。可能大家談到反射面部肌肉都開始抽搐了吧!因為在托管語言里面,最臭名昭著的就是反射!它的性能實在是太低了,甚至在很多時候讓我們無法忍受。不過不用那么糾結了,老陳今天就來分享一下如何來優化反射!
概述
本文涉及到的反射優化的途徑有如下兩種:
- 通過Delegate.CreateDelegate()創建委托進行優化
- 通過.NET4的動態運行時進行優化
如果您還知道其他更加有效的優化途徑,請不吝賜教!
准備工作
今天我們總計要對比五種不同的調用對象成員的方式,也算是一種性能測評。
在開始之前,我們首先定義一個簡單的對象和一個方法,以供測試之用:
1 namespace ReflectionOptimization
2 {
3 public sealed class TestObject
4 {
5 public int Add(int a, int b)
6 {
7 // 簡單演示
8 return a + b;
9 }
10 }
11 }
這個類非常簡單,只提供了一個方法,這個方法返回兩個整形的和。接下來我們看看執行時間測量的代碼,很簡單,想必您已經駕輕就熟了:
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 // 啟動計時器
6 var stopwatch = Stopwatch.StartNew();
7
8 // 運行要測量的代碼
9 action(a, b);
10
11 // 終止計時
12 stopwatch.Stop();
13
14 // 輸出結果
15 Console.WriteLine("{0}: {1}", description, stopwatch.Elapsed.TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
16
17 // 返回執行時間
18 return stopwatch.Elapsed.TotalMilliseconds;
19 }
以上測量時間的方法返回了執行時間,因為我們要在后面用到這個值,在執行多次之后取個平均值,以求測試的公平性、權威性。
編碼實現
首先我們來看看原生反射的實現:
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動態編程的實現:
1 dynamic obj = new TestObject();
2
3 // 有木有發現這個代碼超級簡單?
4 for (var i = 0; i < _TIMES; i++) obj.Add(a, b);
最后我們看看如何使用委托來優化反射:
1 // 委托
2 public delegate int AddMethod(int a, int b);
3
4 // 實現
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);
上面的代碼看起來多了幾行,而且還需要自定義一個委托,寫起來挺麻煩的。因此我們的測試代碼里面還實現了另外一種形式,其實它也是委托:
var d = (Func<TestObject, int, int, int>)Delegate.CreateDelegate(typeof(Func<TestObject, int, int, int>), add);
測試總結
我們首先在Debug模式下將整個測試代碼運行5遍,然后分別記錄平均值,然后再到Release模式下重復該測試。
測試的過程不再闡述,測試結果整理如下:
Debug模式:
調用方式 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 |
---|---|---|---|---|---|
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模式:
調用方式 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 |
---|---|---|---|---|---|
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 |
點評&結論:
- 使用委托優化反射之后,其性能與直接調用相差無幾,保持在同一個數量級之內,對性能要求極度苛刻時推薦此方案;
- 顯式委托(Delegate)和匿名委托(Func)性能差異非常不明顯,但顯式委托的性能還是好一點;
- 原生反射比直接調用慢出了兩個數量級,性能差異達到了200倍之多!
- .NET 4的動態編程語法相當簡潔,其性能只比直接調用高出一個數量級,由於其語法相當簡潔,我們推薦這種做法!
- 原生反射技術在Debug模式和Release模式下沒有太大差異,但其他方式有較為明顯的優化效果(請思考為什么);
- 雖然我們今天的測試不能完全意味着反射優化之后可以和直接調用相媲美,但至少可以從某種程度上擊敗那些個謠言——誰說反射就一定會慢(嘻嘻)!
代碼下載:淺談反射優化.zip