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

简介:

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

 
  1. class Program 
  2. static void Main(string[] args) 
  3. //打印One Word 
  4. string str1 = "One"
  5. str1 += " Word"
  6. Console.WriteLine(str1); 
  7.  
  8. string str2 = "One"+" Word"
  9. Console.WriteLine(str2); 
  10.  
  11. //打印One Word43 
  12. int i = 43; 
  13. Console.WriteLine(str1+i); 
  14.  
  15. Console.WriteLine(str1 + i.ToString()); 

        二、上面的C#生成的CIL代码如下:

 
  1. .method private hidebysig static void Main(string[] args) cil managed 
  2. //初始化 
  3. .entrypoint 
  4. // 代码大小 80 (0x50) 
  5. .maxstack 2 
  6. .locals init ([0] string str1, 
  7. [1] string str2, 
  8. [2] int32 i) 
  9.  
  10. //两个字符串分为两个步骤相加 
  11. IL_0000: nop 
  12. IL_0001: ldstr "One" 
  13. IL_0006: stloc.0 
  14. IL_0007: ldloc.0 
  15. IL_0008: ldstr " Word" 
  16. IL_000d: call string [mscorlib]System.String::Concat(string, 
  17. string) 
  18. IL_0012: stloc.0 
  19. IL_0013: ldloc.0 
  20. IL_0014: call void [mscorlib]System.Console::WriteLine(string) 
  21.  
  22. //两个字符串在同一行里面进行相加 被翻译为CIL时就已经连接到一起 
  23. IL_0019: nop 
  24. IL_001a: ldstr "One Word" 
  25. IL_001f: stloc.1 
  26. IL_0020: ldloc.1 
  27. IL_0021: call void [mscorlib]System.Console::WriteLine(string) 
  28.  
  29. //将Int32字符装箱再和String合并打印 
  30. IL_0026: nop 
  31. IL_0027: ldc.i4.s 43 
  32. IL_0029: stloc.2 
  33. IL_002a: ldloc.0 
  34. IL_002b: ldloc.2 
  35. IL_002c: box [mscorlib]System.Int32 
  36. IL_0031: call string [mscorlib]System.String::Concat(object, 
  37. object) 
  38. IL_0036: call void [mscorlib]System.Console::WriteLine(string) 
  39.  
  40. //直接调用数字的ToString()方法 
  41. IL_003b: nop 
  42. IL_003c: ldloc.0 
  43. IL_003d: ldloca.s i 
  44. IL_003f: call instance string [mscorlib]System.Int32::ToString() 
  45. IL_0044: call string [mscorlib]System.String::Concat(string, 
  46. string) 
  47. IL_0049: call void [mscorlib]System.Console::WriteLine(string) 
  48. IL_004e: nop 
  49. IL_004f: ret 
  50. } // end of method Program::Main 

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

              第1种

 
  1. //打印One Word 
  2. string str1 = "One"
  3. str1 += " Word"
  4. Console.WriteLine(str1); 

 

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

             第2种

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

 

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

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

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

               第1种

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

                 第2种

 
  1. int i = 43;  
  2. Console.WriteLine(str1 + i.ToString());  
  3.  
  4. //直接调用数字的ToString()方法  
  5. IL_003b: nop  
  6. IL_003c: ldloc.0  
  7. IL_003d: ldloca.s i  
  8. //这里没有装箱的操作,仅仅调用了重载的ToString()方法  
  9. IL_003f: call instance string [mscorlib]System.Int32::ToString()  
  10. IL_0044: call string [mscorlib]System.String::Concat(string,  
  11. string)  
  12. IL_0049: call void [mscorlib]System.Console::WriteLine(string)  
  13. IL_004e: nop  

        结论:在这里我们分析第一种情况是对Int32值进行了box装箱操作,然后调用系统函数和第一个 string相加才得到结果的。而第二种情况是直接对Int32调用其重载函数ToString()得到Int32值的字符再与第一个字符相加。在这里我 们避免了装箱分配堆地址查找空余堆地址等麻烦,所以如果再一个循环语句中,第二种情况肯定效率要高很多。


本文转自程兴亮 51CTO博客,原文链接:http://blog.51cto.com/chengxingliang/826595


相关文章
|
1月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
1月前
Visual Studio 快速分析 .NET Dump 文件
【11月更文挑战第10天】.NET Dump 文件是在 .NET 应用程序崩溃或出现问题时生成的,记录了应用程序的状态,包括内存对象、线程栈和模块信息。通过分析这些文件,开发人员可以定位和解决内存泄漏、死锁等问题。在 Visual Studio 中,可以通过调试工具、内存分析工具和符号加载等功能来详细分析 Dump 文件。此外,还可以使用第三方工具如 WinDbg 进行更深入的分析。
|
5月前
|
存储 运维
使用Visual Studio分析.NET Dump
使用Visual Studio分析.NET Dump
|
3月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
4月前
|
存储 C#
揭秘C#.Net编程秘宝:结构体类型Struct,让你的数据结构秒变高效战斗机,编程界的新星就是你!
【8月更文挑战第4天】在C#编程中,结构体(`struct`)是一种整合多种数据类型的复合数据类型。与类不同,结构体是值类型,意味着数据被直接复制而非引用。这使其适合表示小型、固定的数据结构如点坐标。结构体默认私有成员且不可变,除非明确指定。通过`struct`关键字定义,可以包含字段、构造函数及方法。例如,定义一个表示二维点的结构体,并实现计算距离原点的方法。使用时如同普通类型,可通过实例化并调用其成员。设计时推荐保持结构体不可变以避免副作用,并注意装箱拆箱可能导致的性能影响。掌握结构体有助于构建高效的应用程序。
133 7
|
4月前
|
开发框架 缓存 .NET
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
【App Service】在Azure App Service中分析.NET应用程序的性能的好帮手(Review Stack Traces)
|
5月前
|
开发框架 .NET API
.NET Core 和 .NET 标准类库项目类型有什么区别?
在 Visual Studio 中,可创建三种类库:.NET Framework、.NET Standard 和 .NET Core。.NET Standard 是规范,确保跨.NET实现的API一致性,适用于代码共享。.NET Framework 用于特定技术,如旧版支持。.NET Core 库允许访问更多API但限制兼容性。选择取决于兼容性和所需API:需要广泛兼容性时用.NET Standard,需要更多API时用.NET Core。.NET Standard 替代了 PCL,促进多平台共享代码。
|
7月前
|
编译器 C#
C#.Net筑基-类型系统②常见类型 --record是什么类型?
`record`在C#中是一种创建简单、只读数据结构的方式,常用于轻量级数据传输。它本质上是类(默认)或结构体的快捷形式,包含自动生成的属性、`Equals`、`ToString`、解构赋值等方法。记录类型可以继承其他record或接口,但不继承普通类。支持使用`with`语句创建副本。例如,`public record User(string Name, int Age)`会被编译为包含属性、相等比较和`ToString()`等方法的类。记录类型提供了解构赋值和自定义实现,如密封的`sealed`记录,防止子类重写。
|
7月前
|
存储 安全 Unix
C#.Net筑基-类型系统②常见类型--日期和时间的故事
在System命名空间中,有几种表示日期时间的不可变结构体(Struct):DateTime、DateTimeOffset、TimeSpan、DateOnly和TimeOnly。DateTime包含当前本地或UTC时间,以及最小和最大值;DateTimeOffset增加了时区偏移信息,适合跨时区操作。UTC是世界标准时间,而格林尼治标准时间(GMT)不稳定,已被更精确的UTC取代。DateTimeOffset和DateTime提供了转换为UTC和本地时间的方法,以及各种解析和格式化函数。
|
6月前
|
存储 编译器
【.NET Core】可为null类型详解
【.NET Core】可为null类型详解
221 0