zoukankan      html  css  js  c++  java
  • 由浅入深CIL系列:6.For和Foreach的CIL结构组成以及运行效率

            本节即将新接触的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#代码如下:

    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的区别。最后对于效率的比较可能和环境等有比较大的差异,大家可以不放可以自己建立一个控制台程序试试。

  • 相关阅读:
    python每日一题:使用套接字创建分布式进程
    市盈率分析
    python每日一题:分布式进程之坑点
    python每日一题:比较单线程,多线程,协程的运行效率
    python每日一题:锁知识点
    python每日一题:查找一篇文档中的人名和城市
    python之装饰器@
    python每日一题:利用字典实现超市购物程序
    【Java基础】多线程
    【Java基础】异常处理
  • 原文地址:https://www.cnblogs.com/chengxingliang/p/2096281.html
Copyright © 2011-2022 走看看