• 由浅入深CIL系列:4.抛砖引玉:使用CIL来分析string类型在.NET运算中的性能和避免装箱


            一、在.NET中string是一种特殊的引用类型,它一旦被赋值在堆上的地址即不可改变,之后对其进行的字符串相加等操作之后的结果都指向另外一个堆地址,而非原来的字符串地址。现在我们看以下一段C#代码以观察string在实际编码过程中的使用。

    class Program
    {
    static void Main(string[] args)
    {
    //打印One Word
    string str1 = "One";
    str1
    += " Word";
    Console.WriteLine(str1);

    string str2 = "One"+" Word";
    Console.WriteLine(str2);

    //打印One Word43
    int i = 43;
    Console.WriteLine(str1
    +i);

    Console.WriteLine(str1
    + i.ToString());
    }
    }
            二、上面的C#生成的CIL代码如下:
    .method private hidebysig static void Main(string[] args) cil managed
    {
    //初始化
    .entrypoint
    // 代码大小 80 (0x50)
    .maxstack 2
    .locals init ([
    0] string str1,
    [
    1] string str2,
    [
    2] int32 i)

    //两个字符串分为两个步骤相加
    IL_0000: nop
    IL_0001: ldstr
    "One"
    IL_0006: stloc.
    0
    IL_0007: ldloc.
    0
    IL_0008: ldstr
    " Word"
    IL_000d: call
    string [mscorlib]System.String::Concat(string,
    string)
    IL_0012: stloc.
    0
    IL_0013: ldloc.
    0
    IL_0014: call
    void [mscorlib]System.Console::WriteLine(string)

    //两个字符串在同一行里面进行相加 被翻译为CIL时就已经连接到一起
    IL_0019: nop
    IL_001a: ldstr
    "One Word"
    IL_001f: stloc.
    1
    IL_0020: ldloc.
    1
    IL_0021: call
    void [mscorlib]System.Console::WriteLine(string)

    //将Int32字符装箱再和String合并打印
    IL_0026: nop
    IL_0027: ldc.i4.s
    43
    IL_0029: stloc.
    2
    IL_002a: ldloc.
    0
    IL_002b: ldloc.
    2
    IL_002c: box [mscorlib]System.Int32
    IL_0031: call
    string [mscorlib]System.String::Concat(object,
    object)
    IL_0036: call
    void [mscorlib]System.Console::WriteLine(string)

    //直接调用数字的ToString()方法
    IL_003b: nop
    IL_003c: ldloc.
    0
    IL_003d: ldloca.s i
    IL_003f: call instance
    string [mscorlib]System.Int32::ToString()
    IL_0044: call
    string [mscorlib]System.String::Concat(string,
    string)
    IL_0049: call
    void [mscorlib]System.Console::WriteLine(string)
    IL_004e: nop
    IL_004f: ret
    }
    // end of method Program::Main

            三、首先我们看两种字符串的构造方式的不同而引起的效能变化。

                  第1种

    //打印One Word
    string str1 = "One";
    str1
    += " Word";
    Console.WriteLine(str1);
    //第一种、两个字符串分为两个步骤相加
    IL_0000: nop
    IL_0001: ldstr
    "One" //将One字符串存到堆上并且将其引用压栈
    IL_0006: stloc.0 //从栈顶部的One字符串引用地址弹出到第一个参数0
    IL_0007: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上
    IL_0008: ldstr " Word" //将Word字符串存到堆上并且将其引用返回到计算机栈上
    //调用系统函数将上面两个字符串相加其结果存到栈顶
    IL_000d: call string [mscorlib]System.String::Concat(string,
    string)
    IL_0012: stloc.
    0 //从栈顶部的One字符串引用地址弹出到第一个参数0
    IL_0013: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上
    //调用系统参数打印One Word
    IL_0014: call void [mscorlib]System.Console::WriteLine(string)

                 第2种

    string str2 = "One"+" Word";
    Console.WriteLine(str2);
    //两个字符串在同一行里面进行相加 被翻译为CIL时就已经连接到一起
    IL_0019: nop
    IL_001a: ldstr
    "One Word" //直接已经构造成One Word
    IL_001f: stloc.1 //从栈顶部的One Word字符串引用地址弹出到第一个参数1
    IL_0020: ldloc.1 //将索引 1 处的局部变量加载到计算堆栈上
    //调用系统参数打印One Word
    IL_0021: call void [mscorlib]System.Console::WriteLine(string)

            结论:通过上面两种方式构造方式的CIL我们可以很清晰的看出第二种方式的效率要高于第一种的字符串构造方式。所以我们在实际的编码过程中可以考虑尽量使用第二种编码方式。

            四、大家都知道装箱操作会在堆上寻找一个控件来存储值类型的值。会耗费大量的时间。所以下面我们来看两个实例代码

                   第1种

    int i = 43;
    Console.WriteLine(str1
    +i);
    //将Int32字符装箱再和String合并打印
    IL_0026: nop
    IL_0027: ldc.i4.s
    43
    IL_0029: stloc.
    2
    IL_002a: ldloc.
    0
    IL_002b: ldloc.
    2
    IL_002c: box [mscorlib]System.Int32
    //在这里有一个装箱的操作
    IL_0031: call string [mscorlib]System.String::Concat(object,
    object)
    IL_0036: call
    void [mscorlib]System.Console::WriteLine(string)

                     第2种

    int i = 43;
    Console.WriteLine(str1
    + i.ToString());
    //直接调用数字的ToString()方法
    IL_003b: nop
    IL_003c: ldloc.
    0
    IL_003d: ldloca.s i
    //这里没有装箱的操作,仅仅调用了重载的ToString()方法
    IL_003f: call instance string [mscorlib]System.Int32::ToString()
    IL_0044: call
    string [mscorlib]System.String::Concat(string,
    string)
    IL_0049: call
    void [mscorlib]System.Console::WriteLine(string)
    IL_004e: nop
            结论:在这里我们分析第一种情况是对Int32值进行了box装箱操作,然后调用系统函数和第一个string相加才得到结果的。而第二种情况是直接对Int32调用其重载函数ToString()得到Int32值的字符再与第一个字符相加。在这里我们避免了装箱分配堆地址查找空余堆地址等麻烦,所以如果再一个循环语句中,第二种情况肯定效率要高很多。
  • 相关阅读:
    下载flash我的三种方法
    随机变换背景图象(一个可以刷新心情的特效)
    禁止缓存
    [模板]字符串算法
    [学习笔记]有上下界的网络流
    [bzoj2809][Apio2012]dispatching
    [四校联考]Easy Problems
    [学习笔记]tarjan
    [vijos1780][NOIP2012]开车旅行
    记第一次打女队
  • 原文地址:https://www.cnblogs.com/chengxingliang/p/2092821.html
Copyright © 2020-2023  润新知