由浅入深CIL系列:6.For和Foreach的CIL结构组成以及运行效率
2011-07-07 12:22
417 查看
本节即将新接触的CIL操作符如下:
br.s IL_003c 无条件地将控制转移到目标指令(短格式)
clt 从计算堆栈的顶部弹出当前值并将其存储到索引 2 处的局部变量列表中
ldloca.s CS$5$0001 将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)
leave.s 退出受保护的代码区域,无条件将控制转移到目标指令(缩写形式)
constrained. 约束要对其进行虚方法调用的类型
endfinally 将控制从异常块的 fault 或 finally 子句转移回公共语言结构 (CLI) 异常处理程序
在C#中我们经常会遇到遍历数组、遍历List<>、遍历HashTable等情况,在本文中我们首先构造一个List<int>对象,然后通过For和Foreach来遍历它看看他们之间的CIL代码有什么区别和不同。
首先我们贴出C#代码如下:
其次我们来看CIL代码如下所示,因为CIL代码有点儿长,设置为隐藏有需要的可以点击查看:
所有的CIL代码
再次我们来分析这个CIL代码,他分为三大部分,第一部分为List的初始化,第二部分为For循环遍历List,第二部分为Foreach遍历循环,首先我们看for循环的CIL代码如下所示:
下面我们来看看Foreach方式的遍历的CIL代码如下:
从这里我们可以看出for方式的遍历是直接对元素集合本身的遍历,而foreach方式的遍历是对获取到元素集合的实现IEnumerator接口的对象,通过这个Current属性,调用MoveNext()函数对集合进行遍历的。
下面我们来看看for和foreach对List对象的不同数据量级别的访问时间如下,首先我们看耗时测算代码如下:
时间耗时测算代码
首先List<int> listInt为100的耗时如下三图:
其次List<int> listInt为10000的耗时如下三图:
最后我们看看List<int> listInt为10000的耗时如下三图:
结语:通过本篇文章的CIL我们知道了for和foreach在.NET环境的中间语言中是如何控制和循环的,另外也更加深入的了解for和foreach的区别。最后对于效率的比较可能和环境等有比较大的差异,大家可以不放可以自己建立一个控制台程序试试。
br.s IL_003c 无条件地将控制转移到目标指令(短格式)
clt 从计算堆栈的顶部弹出当前值并将其存储到索引 2 处的局部变量列表中
ldloca.s CS$5$0001 将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)
leave.s 退出受保护的代码区域,无条件将控制转移到目标指令(缩写形式)
constrained. 约束要对其进行虚方法调用的类型
endfinally 将控制从异常块的 fault 或 finally 子句转移回公共语言结构 (CLI) 异常处理程序
在C#中我们经常会遇到遍历数组、遍历List<>、遍历HashTable等情况,在本文中我们首先构造一个List<int>对象,然后通过For和Foreach来遍历它看看他们之间的CIL代码有什么区别和不同。
首先我们贴出C#代码如下:
class Program { static void Main(string[] args) { //初始化一个List<int> List<int> listInt = new List<int>(); for (int i = 0; i < 100000; i++) { listInt.Add(i + 1); } Console.WriteLine("--------------------------"); //第一种for遍历 for (int i = 0; i < listInt.Count; i++) { } //第二种foreach遍历 foreach (int i in listInt) { } } }
其次我们来看CIL代码如下所示,因为CIL代码有点儿长,设置为隐藏有需要的可以点击查看:
所有的CIL代码
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 123 (0x7b) .maxstack 3 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> listInt, [1] int32 i, [2] bool CS$4$0000, [3] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> CS$5$0001) IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() IL_0006: stloc.0 IL_0007: ldc.i4.0 IL_0008: stloc.1 IL_0009: br.s IL_001b IL_000b: nop IL_000c: ldloc.0 IL_000d: ldloc.1 IL_000e: ldc.i4.1 IL_000f: add IL_0010: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) IL_0015: nop IL_0016: nop IL_0017: ldloc.1 IL_0018: ldc.i4.1 IL_0019: add IL_001a: stloc.1 IL_001b: ldloc.1 IL_001c: ldc.i4 0x186a0 IL_0021: clt IL_0023: stloc.2 IL_0024: ldloc.2 IL_0025: brtrue.s IL_000b IL_0027: ldstr "--------------------------" IL_002c: call void [mscorlib]System.Console::WriteLine(string) IL_0031: nop IL_0032: ldc.i4.0 IL_0033: stloc.1 IL_0034: br.s IL_003c IL_0036: nop IL_0037: nop IL_0038: ldloc.1 IL_0039: ldc.i4.1 IL_003a: add IL_003b: stloc.1 IL_003c: ldloc.1 IL_003d: ldloc.0 IL_003e: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count() IL_0043: clt IL_0045: stloc.2 IL_0046: ldloc.2 IL_0047: brtrue.s IL_0036 IL_0049: nop IL_004a: ldloc.0 IL_004b: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() IL_0050: stloc.3 .try { IL_0051: br.s IL_005d IL_0053: ldloca.s CS$5$0001 IL_0055: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() IL_005a: stloc.1 IL_005b: nop IL_005c: nop IL_005d: ldloca.s CS$5$0001 IL_005f: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext() IL_0064: stloc.2 IL_0065: ldloc.2 IL_0066: brtrue.s IL_0053 IL_0068: leave.s IL_0079 } // end .try finally { IL_006a: ldloca.s CS$5$0001 IL_006c: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> IL_0072: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0077: nop IL_0078: endfinally } // end handler IL_0079: nop IL_007a: ret } // end of method Program::Main
再次我们来分析这个CIL代码,他分为三大部分,第一部分为List的初始化,第二部分为For循环遍历List,第二部分为Foreach遍历循环,首先我们看for循环的CIL代码如下所示:
//第一种for遍历 IL_0031: nop //将整数值 0 作为 int32 推送到计算堆栈上 IL_0032: ldc.i4.0 //从计算堆栈的顶部弹出当前值并将其存储到索引 1 处的局部变量列表中 IL_0033: stloc.1 //无条件地将控制转移到目标指令(短格式)。 IL_0034: br.s IL_003c IL_0036: nop ///////////注意:这里就是循环内部需要处理的代码处,在本实例中无代码 IL_0037: nop //将索引 1 处的局部变量加载到计算堆栈上 IL_0038: ldloc.1 //将整数值 1 作为 int32 推送到计算堆栈上 IL_0039: ldc.i4.1 //将两个值相加并将结果推送到计算堆栈上。 IL_003a: add //从计算堆栈的顶部弹出当前值并将其存储到索引 1 处的局部变量列表中 IL_003b: stloc.1 //将索引 1 处的局部变量加载到计算堆栈上 IL_003c: ldloc.1 //将索引 0 处的局部变量加载到计算堆栈上 IL_003d: ldloc.0 //调用系统函数获取List数量 IL_003e: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count() //比较两个值。如果第一个值小于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。 IL_0043: clt //从计算堆栈的顶部弹出当前值并将其存储到索引 2 处的局部变量列表中 IL_0045: stloc.2 //将索引 2 处的局部变量加载到计算堆栈上 IL_0046: ldloc.2 //如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。 IL_0047: brtrue.s IL_0036
下面我们来看看Foreach方式的遍历的CIL代码如下:
//第二种foreach遍历 IL_0049: nop //将索引 0 处的局部变量加载到计算堆栈上。 IL_004a: ldloc.0 //调用List<int> listInt对象的GetEnumerator()方法 //任何集合类对象都有一个GetEnumerator()方法,该方法可以返回一个实现了 IEnumerator接口的对象, //这个返回的IEnumerator对象既不是集合类对象,也不是集合的元素类对象,它是一个独立的类对象。 //通过这个对象,可以遍历访问集合类对象中的每一个元素对象 . IL_004b: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator() //从计算堆栈的顶部弹出当前值并将其存储到索引 3 处的局部变量列表中 IL_0050: stloc.3 .try { //无条件地将控制转移到目标指令(短格式)。 IL_0051: br.s IL_005d //将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)。 IL_0053: ldloca.s CS$5$0001 //调用get_Current()函数返回一个Object类型。 IL_0055: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() //从计算堆栈的顶部弹出当前值并将其存储到索引 1 处的局部变量列表中 IL_005a: stloc.1 IL_005b: nop ///////////注意:这里就是循环内部需要处理的代码处,在本实例中无代码 IL_005c: nop //将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)。 IL_005d: ldloca.s CS$5$0001 //调用MoveNext()函数运行到下一个元素 IL_005f: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext() //从计算堆栈的顶部弹出当前值并将其存储到索引 2 处的局部变量列表中 IL_0064: stloc.2 //将索引 2 处的局部变量加载到计算堆栈上 IL_0065: ldloc.2 //如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。 IL_0066: brtrue.s IL_0053 //退出受保护的代码区域,无条件将控制转移到目标指令(缩写形式)。 IL_0068: leave.s IL_0079 } // end .try finally { //将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)。 IL_006a: ldloca.s CS$5$0001 //约束要对其进行虚方法调用的类型 IL_006c: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> //调用Dispose()将IEnumerator对象Dispose掉 IL_0072: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0077: nop //将控制从异常块的 fault 或 finally 子句转移回公共语言结构 (CLI) 异常处理程序 IL_0078: endfinally } // end handler
从这里我们可以看出for方式的遍历是直接对元素集合本身的遍历,而foreach方式的遍历是对获取到元素集合的实现IEnumerator接口的对象,通过这个Current属性,调用MoveNext()函数对集合进行遍历的。
下面我们来看看for和foreach对List对象的不同数据量级别的访问时间如下,首先我们看耗时测算代码如下:
时间耗时测算代码
class Program { static void Main(string[] args) { //初始化一个List<int> List<int> listInt = new List<int>(); for (int i = 0; i < 1000000; i++) { listInt.Add(i + 1); } Console.WriteLine("--------------------------"); //第一种for遍历 Stopwatch sw1 = new Stopwatch(); sw1.Start(); for (int i = 0; i < listInt.Count; i++) { } sw1.Stop(); Stopwatch sw2 = new Stopwatch(); sw2.Start(); //第二种foreach遍历 foreach (int i in listInt) { } sw2.Stop(); Console.WriteLine("当前得List<int>对象数目:"+listInt.Count.ToString()); Console.WriteLine(@"for 的遍历消耗时间是:" + sw1.Elapsed); Console.WriteLine(@"foreach 的遍历消耗时间是:" + sw2.Elapsed); Console.ReadLine(); } }
首先List<int> listInt为100的耗时如下三图:
其次List<int> listInt为10000的耗时如下三图:
最后我们看看List<int> listInt为10000的耗时如下三图:
结语:通过本篇文章的CIL我们知道了for和foreach在.NET环境的中间语言中是如何控制和循环的,另外也更加深入的了解for和foreach的区别。最后对于效率的比较可能和环境等有比较大的差异,大家可以不放可以自己建立一个控制台程序试试。
相关文章推荐
- 由浅入深CIL系列:6.For和Foreach的CIL结构组成以及运行效率
- php代码优化系列 -- array_walk 和 foreach, for 的效率的比较
- php代码优化系列(二) -- array_walk 和 foreach, for 的效率的比较
- 由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
- 由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
- PHP 数组的遍历的几种方式(以及foreach与for/while+each效率的比较)
- javascript中for、each以及foreach的效率对比
- 数组、List、ArrayList的使用以及for、foreach效率比较
- php代码优化系列(二) -- array_walk 和 foreach, for 的效率的比较
- Linux系统基本结构之一 内核的组成以及运行原理
- 了解For、Foreach的运行过程
- C# for 和 foreach的执行效率
- 由浅入深CIL系列:1.初步认识CIL通用中间语言
- java的选择结构switch和循环结构for while do while以及流程控制语句break continue return
- foreach与for效率问题
- for、foreach和MoveNext循环效率粗比较
- 由浅入深CIL系列【目录索引】+ PostSharp AOP编程【目录索引】
- 看看软件开发人员在代码编写工作中为什么需要学习代码的优雅性,结构以及效率方面的内容。
- for和foreach的执行效率的问题之新发现
- libcef学习最详细的入门资料系列之二 :libcef的编译和运行,以及MFC下的实现