写完《基于动态代码生成技术的动态对象工厂》一文后我一直很奇怪为什么动态代码生成的方法相对于直接使用new有如此大的劣势?道理上说动态代码一旦生成,那么它和原生代码应该也没什么区别了,那么它的执行效率应该和相应的原生代码差别不大才对。仔细观察Creator.New版本的执行过程,我猜测问题并不是出在动态生成的代码本身,而是出在周围的代码上。在我的测试程序中,我对以下代码返复调用1000万次:
A a = (A)Creator.New(typeof(A), "a");
而Createor.New方法中要对一个SortedList进行查找,找到满足条件的Creator子类实例,然后调用它的CreateObject方法来产生对象,此查找的过程可能会很影响效率,为此我把测试部分的代码改为如下:
object[] param = new object[] ...{ "a" };
Creator creator = Creator.GetCreator(typeof(A), param);
DateTime dt3 = DateTime.Now;
for (int i = 0; i < count; i++)
...{
A a = (A)creator.CreateObject(param);
}
DateTime dt4 = DateTime.Now;
显然这样做给了动态代码生成方式很大的优待,我首先取得了合适的creator子类对象,并且已经把要传的参数数组准备好了,循环里面所做的只是单纯调用动态生成的代码。不过这没关系,我就是要看看在极优环境下动态生成的代码的执行效率如何,相比直接使用new会差多少。相应的,对直接使用new方法的测试代码如下:
优化后重复一亿次所需时间(毫秒)
调用方式 |
Creator.CreateObject |
直接使用new |
引用类型参数 |
1个参数 |
2312.5 |
1093.75 |
3个参数 |
2593.75 |
1109.375 |
9个参数 |
2687.5 |
1093.75 |
值类型参数 |
1个参数 |
1937.5 |
1078.125 |
3个参数 |
3000 |
1093.75 |
9个参数 |
5343.75 |
1093.75 |
下面是关于此结果的几点分析:
1 此次测试结果明显好于上一次,动态生成的代码运行时间只是直接使用new的2-4倍。可见前面所做的分析是正确的,执行动态代码本身和执行原生代码在效率上没有什么差别。
2 之所以仍有2-4倍的差别,我觉得是因为动态生成的代码在参数进栈时需要先从数组中取得参数才能进栈,这样显然比直接使用new时用ldstr或类似指令要慢一些,2-4倍的差别比较正常
3 参数为引用类型动态代码的时间只是new的2倍多,而参数为值类型时倍数则较多,这是因为参数为引用类型时在从数组中取值之后需要一个拆箱操作,显然此操作是非常慢的。
4 调用动态代码时参数个数不同时所需时间也会增加,这个和预想的一样,因为由动态生成代码的部分可以看出,参数越多for循环执行的次数也越多,生成的IL代码就越长。
5 代码执行时间并不随参数的个数成倍数关系增长,这里大概因为从数组中取值的速度比较快,而newobj指令是最慢的,因此参数个数的影响不是很大。但参数为值类型时,时间增长的较快,这更说明了拆箱操作是很慢的,对结果的影响较大
6 参数为一个值类型时比参数为一个引用类型时动态代码所用的时间更短(值类型时为1937.5),引用类型时为(2312.5),但直观想象的话应该是引用类型的快一些才对,因为它不需要拆箱,并且拆箱也是个慢操作,但这与测试结果不符,我还不太清楚这是为什么。
7因同一段代码返复测试时时间也会有一些浮动,因此可以认为各种不同参数时new方法的时间几乎没有差别,可见像ldstr、ldc.i4这样的压栈操作速度是非常快的,相对于newobj来说几乎可以忽略不计。
查看本文来源