由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?

简介:

本节将接触几个新的CIL操作码如下

              ldc.i4.0    将整数值 0 作为 int32 推送到计算堆栈上

              Ceq         比较两个值。如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上。

              Brtrue.s   如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。

              Brfalse.S  如果 value 为 false、空引用或零,则将控制转移到目标指令。

              Callvirt     对对象调用后期绑定方法,并且将返回值推送到计算堆栈上。

              Ldsfld      将静态字段的值推送到计算堆栈上。

源代码

        一、在.NET有几种判断string是否为空的方法,也有两种判断值是否相等的方法。下面我们来看看:

 
  1. class Program 
  2. static void Main(string[] args) 
  3. //判断字符串是否为空 
  4. string str1 = "MyWord"
  5. if (str1 == ""
  6. if (str1 == string.Empty) 
  7.             if (str1 != null && str1.Length== 0) 
  8.                 ; 
  9.             if (string.IsNullOrEmpty(str1)) 
  10.                 ; 
  11.    } 

        二、下面我们看看上面的Cs代码生成的CIL代码如下:

 
  1. .method private hidebysig static void Main(string[] args) cil managed 
  2. .entrypoint 
  3. // 代码大小 85 (0x55) 
  4. .maxstack 2 
  5. //声明3个参数,分别是str1和bool值 
  6. .locals init ([0] string str1, 
  7. [1] bool CS$4$0000) 
  8. IL_0000: nop 
  9. //推送对元数据中存储的"MyWord"字符串的新对象引用 
  10. IL_0001: ldstr "MyWord" 
  11. //将"MyWord"压栈到参数0 
  12. IL_0006: stloc.0 
  13.   
  14.  
  15.   
  16.  
  17.  
  18.  //--------string空判断第一种方法  if (str1 == "")  -------- 
  19. //将"MyWord"从参数0处加载到计算堆栈上  
  20.   IL_0007:  ldloc.0 
  21. //推送对元数据中存储的""字符串的新对象引用 
  22.   IL_0008:  ldstr      "" 
  23. //通过System.String::op_Equality函数判断是否相等 
  24.   IL_000d:  call       bool [mscorlib]System.String::op_Equality(string, 
  25.                                                                  string) 
  26. //将整数值 0 作为 int32 推送到计算堆栈上 
  27.   IL_0012:  ldc.i4.0 
  28. //ceq比较两个值。如果这两个值相等,则将整数值 1 (int32)推送到计算堆栈上; 
  29. //否则,将 0 (int32) 推送到计算堆栈上。  
  30.   IL_0013:  ceq 
  31. //将true或者false的bool值弹出栈存到参数1去 
  32.   IL_0015:  stloc.1 
  33. //从参数1中加载数据到计算堆栈上去   
  34.   IL_0016:  ldloc.1 
  35. //如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。 
  36. //也就是if判断中如果结果为true的话,则运行内部代码  
  37.   IL_0017:  brtrue.s   IL_0019 
  38.  
  39.  
  40.  
  41.  //--------string空判断第二种方法  if (str1 == string.Empty)  -------- 
  42.   IL_0019:  ldloc.0 
  43. //Ldsfld 将静态字段的值推送到计算堆栈上。 
  44.   IL_001a:  ldsfld     string [mscorlib]System.String::Empty 
  45.   IL_001f:  call       bool [mscorlib]System.String::op_Equality(string, 
  46.                                                                  string) 
  47.   IL_0024:  ldc.i4.0 
  48.   IL_0025:  ceq 
  49.   IL_0027:  stloc.1 
  50.   IL_0028:  ldloc.1 
  51.   IL_0029:  brtrue.s   IL_002b 
  52.  
  53.  
  54.  
  55.  
  56.  //--------string空判断第三种方法  if (str1!=null&&str1.Length == 0)  --------   
  57.   IL_002b:  ldloc.0 
  58. //对象调用后期绑定方法,并且将返回值推送到计算堆栈上。<==> str1!=null 
  59.   IL_002c:  brfalse.s  IL_003c 
  60.   IL_002e:  ldloc.0 
  61. //调用系统函数获取长度 
  62.   IL_002f:  callvirt   instance int32 [mscorlib]System.String::get_Length() 
  63.   IL_0034:  ldc.i4.0 
  64.   IL_0035:  ceq 
  65.   IL_0037:  ldc.i4.0 
  66.   IL_0038:  ceq 
  67.   IL_003a:  br.s       IL_003d 
  68.   IL_003c:  ldc.i4.1 
  69.   IL_003d:  stloc.1 
  70.   IL_003e:  ldloc.1 
  71.   IL_003f:  brtrue.s   IL_0041 
  72.  
  73.  
  74.  
  75. //--------string空判断第四种方法  if (string.IsNullOrEmpty(str1))  --------   
  76.   IL_0041:  ldloc.0 
  77. //直接调用系统System.String::IsNullOrEmpty(string)函数比对 
  78.   IL_0042:  call       bool [mscorlib]System.String::IsNullOrEmpty(string) 
  79.   IL_0047:  ldc.i4.0 
  80.   IL_0048:  ceq 
  81.   IL_004a:  stloc.1 
  82.   IL_004b:  ldloc.1 
  83.   IL_004c:  brtrue.s   IL_004e 
  84. } // end of method Program::Main 

 4种方法的CIL分析

              A.if (str1 == ""),在这里我们需要新构造一个""空字符,然后再调用System.String::op_Equality(string,string)函数对str1和空字符进行对比。

 
  1. //--------string空判断第一种方法 if (str1 == "") -------- 
  2. //将"MyWord"从参数0处加载到计算堆栈上 
  3. IL_0007: ldloc.0 
  4. //推送对元数据中存储的""字符串的新对象引用 
  5. IL_0008: ldstr "" 
  6. //通过System.String::op_Equality函数判断是否相等 
  7. IL_000d: call bool [mscorlib]System.String::op_Equality(string, 
  8. string) 
  9. //将整数值 0 作为 int32 推送到计算堆栈上 
  10. IL_0012: ldc.i4.0 
  11. //ceq比较两个值。如果这两个值相等,则将整数值 1 (int32)推送到计算堆栈上; 
  12. //否则,将 0 (int32) 推送到计算堆栈上。 
  13. IL_0013: ceq 
  14. //将true或者false的bool值弹出栈存到参数1去 
  15. IL_0015: stloc.1 
  16. //从参数1中加载数据到计算堆栈上去 
  17. IL_0016: ldloc.1 
  18. //如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。 
  19. //也就是if判断中如果结果为true的话,则运行内部代码 
  20. IL_0017: brtrue.s IL_0019 

              B.if (str1 == string.Empty),在这里我们通过string [mscorlib]System.String::Empty加载一个CIL代码为.field public static initonly string Empty的静态字段,然后让str1和这个静态字段做比较System.String::op_Equality(string,string),以确定是否为空。

 
  1. //--------string空判断第二种方法 if (str1 == string.Empty) -------- 
  2. IL_0019: ldloc.0 
  3. //Ldsfld 将静态字段的值推送到计算堆栈上。 
  4. IL_001a: ldsfld string [mscorlib]System.String::Empty 
  5. IL_001f: call bool [mscorlib]System.String::op_Equality(string, 
  6. string) 
  7. IL_0024: ldc.i4.0 
  8. IL_0025: ceq 
  9. IL_0027: stloc.1 
  10. IL_0028: ldloc.1 
  11. IL_0029: brtrue.s IL_002b 

               C.if (str1.Length == 0),在这里我们调用[mscorlib]System.String::get_Length()函数获取到字符串长度,然后这个长度和0相对比

 
  1. //--------string空判断第三种方法 if (str1!=null&&str1.Length == 0) -------- 
  2. IL_002b: ldloc.0 
  3. //对象调用后期绑定方法,并且将返回值推送到计算堆栈上。<==> str1!=null 
  4. IL_002c: brfalse.s IL_003c 
  5. IL_002e: ldloc.0 
  6. //调用系统函数获取长度 
  7. IL_002f: callvirt instance int32 [mscorlib]System.String::get_Length() 
  8. IL_0034: ldc.i4.0 
  9. IL_0035: ceq 
  10. IL_0037: ldc.i4.0 
  11. IL_0038: ceq 
  12. IL_003a: br.s IL_003d 
  13. IL_003c: ldc.i4.1 
  14. IL_003d: stloc.1 
  15. IL_003e: ldloc.1 
  16. IL_003f: brtrue.s IL_0041 

               D.if (string.IsNullOrEmpty(str1)),这种方式直接调用系统的System.String::IsNullOrEmpty(string)函数直接比对出结果。

 

 
  1. //--------string空判断第四种方法 if (string.IsNullOrEmpty(str1)) -------- 
  2. IL_0041: ldloc.0 
  3. //直接调用系统System.String::IsNullOrEmpty(string)函数比对 
  4. IL_0042: call bool [mscorlib]System.String::IsNullOrEmpty(string) 
  5. IL_0047: ldc.i4.0 
  6. IL_0048: ceq 
  7. IL_004a: stloc.1 
  8. IL_004b: ldloc.1 
  9. IL_004c: brtrue.s IL_004e 

性能分析

        下面我们通过using System.Diagnostics;命名空间下的Stopwatch对象来计算这4种调用方式所消耗的大概时间。

请看cs代码如下:

 

 
  1. class Program 
  2. static void Main(string[] args) 
  3. //判断字符串是否为空 
  4. string str1 = "MyWord"
  5. //第一种方法耗时计算 
  6. Stopwatch sw1 = new Stopwatch(); 
  7. sw1.Start(); 
  8. if (str1 == ""
  9. sw1.Stop(); 
  10. //第二种方法耗时计算 
  11. Stopwatch sw2 = new Stopwatch(); 
  12. sw2.Start(); 
  13. if (str1 == string.Empty) 
  14. sw2.Stop(); 
  15. //第三种方法耗时计算 
  16. Stopwatch sw3 = new Stopwatch(); 
  17. sw3.Start(); 
  18. if (str1!=null&&str1.Length == 0) 
  19. sw3.Stop(); 
  20. //第四种方法耗时计算 
  21. Stopwatch sw4 = new Stopwatch(); 
  22. sw4.Start(); 
  23. if (string.IsNullOrEmpty(str1)) 
  24. sw4.Stop(); 
  25.  
  26. Console.WriteLine(@"if (str1 == "")的判断时间是:" + sw1.Elapsed); 
  27. Console.WriteLine(@"if (str1 == string.Empty)的判断时间是:" + sw2.Elapsed); 
  28. Console.WriteLine(@"if (str1!=null&&str1.Length == 0)的判断时间是:" + sw3.Elapsed); 
  29. Console.WriteLine(@"if (string.IsNullOrEmpty(str1)) 的判断时间是:" + sw4.Elapsed); 
  30. Console.ReadLine(); 
  31.  

        然后我们需要看看结果如何,为了提高精确度,我们运行多次结果,然后就知道哪种方式的效率最高。

下面我们来看在我的电脑上的运行时间情况如下面的图所示:

       然后我将这段代码发我一个朋友那里得到的运行情况如下图所示:

        鉴于时间跨度太小,以及各种运行环境的不同,还有其他一些原因,对于结果和答案都有有所影响,所以上面的运行结果仅做参考。大家也可以将这段测试代码在自己的电脑上运行一下,看看究竟结果如何?

思考:这4种方法的效率究竟谁高谁低?应该如何排序?为什么形成这样的差异?

扩展阅读

       I.1第一种方法和第二种方法都会使用到一个System.String::op_Equality(string,string)方法,这个方法的CIL代码我们使用ILDASM查看mscorlib.dll文件即可:

System.String::op_Equality(string,string)

.method public hidebysig specialname static  bool op_Equality(string a,  string b) cil managed  // 代码大小 8 (0x8)  .maxstack 8  IL_0000: ldarg.0  IL_0001: ldarg.1  IL_0002: call bool System.String::Equals(string,  string)  IL_0007: ret  } // end of method String::op_Equality 

        I.2上面这段IL代码内部调用了bool System.String::Equals(string,string)方法,这个方法的CIL代码如下:

bool System.String::Equals(string,string)
 
  1. .method public hidebysig static bool Equals(string a, 
  2. string b) cil managed 
  3. // 代码大小 22 (0x16) 
  4. .maxstack 8 
  5. IL_0000: ldarg.0 
  6. IL_0001: ldarg.1 
  7. IL_0002: bne.un.s IL_0006 
  8. IL_0004: ldc.i4.1 
  9. IL_0005: ret 
  10. IL_0006: ldarg.0 
  11. IL_0007: brfalse.s IL_000c 
  12. IL_0009: ldarg.1 
  13. IL_000a: brtrue.s IL_000e 
  14. IL_000c: ldc.i4.0 
  15. IL_000d: ret 
  16. IL_000e: ldarg.0 
  17. IL_000f: ldarg.1 
  18. IL_0010: call bool System.String::EqualsHelper(string, 
  19. string) 
  20. IL_0015: ret 
  21. } // end of method String::Equals 

       I.3上面这段IL代码内部调用了bool System.String::EqualsHelper(stringstring) 方法,这个方法的CIL代码如下,其内部调用了多次int32 System.String::get_Length()函数:  

System.String::EqualsHelper(string,string)
 
  1. .method private hidebysig static bool EqualsHelper(string strA, 
  2. string strB) cil managed 
  3. .custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor() = ( 01 00 00 00 ) 
  4. .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency, 
  5. valuetype System.Runtime.ConstrainedExecution.Cer) = ( 01 00 03 00 00 00 01 00 00 00 00 00 ) 
  6. // 代码大小 199 (0xc7) 
  7. .maxstack 3 
  8. .locals init (int32 V_0, 
  9. char& pinned V_1, 
  10. char& pinned V_2, 
  11. char* V_3, 
  12. char* V_4, 
  13. bool V_5) 
  14. IL_0000: ldarg.0 
  15. IL_0001: callvirt instance int32 System.String::get_Length() 
  16. IL_0006: stloc.0 
  17. IL_0007: ldloc.0 
  18. IL_0008: ldarg.1 
  19. IL_0009: callvirt instance int32 System.String::get_Length() 
  20. IL_000e: beq.s IL_0012 
  21. IL_0010: ldc.i4.0 
  22. IL_0011: ret 
  23. IL_0012: ldarg.0 
  24. IL_0013: ldflda char System.String::m_firstChar 
  25. IL_0018: stloc.1 
  26. IL_0019: ldarg.1 
  27. IL_001a: ldflda char System.String::m_firstChar 
  28. IL_001f: stloc.2 
  29. IL_0020: ldloc.1 
  30. IL_0021: conv.i 
  31. IL_0022: stloc.3 
  32. IL_0023: ldloc.2 
  33. IL_0024: conv.i 
  34. IL_0025: stloc.s V_4 
  35. IL_0027: br.s IL_0097 
  36. IL_0029: ldloc.3 
  37. IL_002a: ldind.i4 
  38. IL_002b: ldloc.s V_4 
  39. IL_002d: ldind.i4 
  40. IL_002e: beq.s IL_0038 
  41. IL_0030: ldc.i4.0 
  42. IL_0031: stloc.s V_5 
  43. IL_0033: leave IL_00c4 
  44. IL_0038: ldloc.3 
  45. IL_0039: ldc.i4.4 
  46. IL_003a: conv.i 
  47. IL_003b: add 
  48. IL_003c: ldind.i4 
  49. IL_003d: ldloc.s V_4 
  50. IL_003f: ldc.i4.4 
  51. IL_0040: conv.i 
  52. IL_0041: add 
  53. IL_0042: ldind.i4 
  54. IL_0043: beq.s IL_004a 
  55. IL_0045: ldc.i4.0 
  56. IL_0046: stloc.s V_5 
  57. IL_0048: leave.s IL_00c4 
  58. IL_004a: ldloc.3 
  59. IL_004b: ldc.i4.8 
  60. IL_004c: conv.i 
  61. IL_004d: add 
  62. IL_004e: ldind.i4 
  63. IL_004f: ldloc.s V_4 
  64. IL_0051: ldc.i4.8 
  65. IL_0052: conv.i 
  66. IL_0053: add 
  67. IL_0054: ldind.i4 
  68. IL_0055: beq.s IL_005c 
  69. IL_0057: ldc.i4.0 
  70. IL_0058: stloc.s V_5 
  71. IL_005a: leave.s IL_00c4 
  72. IL_005c: ldloc.3 
  73. IL_005d: ldc.i4.s 12 
  74. IL_005f: conv.i 
  75. IL_0060: add 
  76. IL_0061: ldind.i4 
  77. IL_0062: ldloc.s V_4 
  78. IL_0064: ldc.i4.s 12 
  79. IL_0066: conv.i 
  80. IL_0067: add 
  81. IL_0068: ldind.i4 
  82. IL_0069: beq.s IL_0070 
  83. IL_006b: ldc.i4.0 
  84. IL_006c: stloc.s V_5 
  85. IL_006e: leave.s IL_00c4 
  86. IL_0070: ldloc.3 
  87. IL_0071: ldc.i4.s 16 
  88. IL_0073: conv.i 
  89. IL_0074: add 
  90. IL_0075: ldind.i4 
  91. IL_0076: ldloc.s V_4 
  92. IL_0078: ldc.i4.s 16 
  93. IL_007a: conv.i 
  94. IL_007b: add 
  95. IL_007c: ldind.i4 
  96. IL_007d: beq.s IL_0084 
  97. IL_007f: ldc.i4.0 
  98. IL_0080: stloc.s V_5 
  99. IL_0082: leave.s IL_00c4 
  100. IL_0084: ldloc.3 
  101. IL_0085: ldc.i4.s 20 
  102. IL_0087: conv.i 
  103. IL_0088: add 
  104. IL_0089: stloc.3 
  105. IL_008a: ldloc.s V_4 
  106. IL_008c: ldc.i4.s 20 
  107. IL_008e: conv.i 
  108. IL_008f: add 
  109. IL_0090: stloc.s V_4 
  110. IL_0092: ldloc.0 
  111. IL_0093: ldc.i4.s 10 
  112. IL_0095: sub 
  113. IL_0096: stloc.0 
  114. IL_0097: ldloc.0 
  115. IL_0098: ldc.i4.s 10 
  116. IL_009a: bge.s IL_0029 
  117. IL_009c: br.s IL_00b5 
  118. IL_009e: ldloc.3 
  119. IL_009f: ldind.i4 
  120. IL_00a0: ldloc.s V_4 
  121. IL_00a2: ldind.i4 
  122. IL_00a3: bne.un.s IL_00b9 
  123. IL_00a5: ldloc.3 
  124. IL_00a6: ldc.i4.4 
  125. IL_00a7: conv.i 
  126. IL_00a8: add 
  127. IL_00a9: stloc.3 
  128. IL_00aa: ldloc.s V_4 
  129. IL_00ac: ldc.i4.4 
  130. IL_00ad: conv.i 
  131. IL_00ae: add 
  132. IL_00af: stloc.s V_4 
  133. IL_00b1: ldloc.0 
  134. IL_00b2: ldc.i4.2 
  135. IL_00b3: sub 
  136. IL_00b4: stloc.0 
  137. IL_00b5: ldloc.0 
  138. IL_00b6: ldc.i4.0 
  139. IL_00b7: bgt.s IL_009e 
  140. IL_00b9: ldloc.0 
  141. IL_00ba: ldc.i4.0 
  142. IL_00bb: cgt 
  143. IL_00bd: ldc.i4.0 
  144. IL_00be: ceq 
  145. IL_00c0: stloc.s V_5 
  146. IL_00c2: leave.s IL_00c4 
  147. IL_00c4: ldloc.s V_5 
  148. IL_00c6: ret 
  149. } // end of method String::EqualsHelper 

        II.1在第三种方法的CIL代码中我们调用了一次int32 [mscorlib]System.String::get_Length()函数.

        III.1在第四种方法的CIL代码中调用了一次bool [mscorlib]System.String::IsNullOrEmpty(string)函数,此函数的CIL代码如下,它内部调用了一次System.String::get_Length()函数

System.String::IsNullOrEmpty(string)

 
  1. .method public hidebysig static bool IsNullOrEmpty(string 'value') cil managed 
  2. // 代码大小 15 (0xf) 
  3. .maxstack 8 
  4. IL_0000: ldarg.0 
  5. IL_0001: brfalse.s IL_000d 
  6. IL_0003: ldarg.0 
  7. IL_0004: callvirt instance int32 System.String::get_Length() 
  8. IL_0009: ldc.i4.0 
  9. IL_000a: ceq 
  10. IL_000c: ret 
  11. IL_000d: ldc.i4.1 
  12. IL_000e: ret 
  13. } // end of method String::IsNullOrEmpty 

本文转自程兴亮 51CTO博客,原文链接:
http://blog.51cto.com/chengxingliang/826603
相关文章
|
4月前
|
C++
C++初阶(十二)string的模拟实现
C++初阶(十二)string的模拟实现
26 0
|
5月前
SAP ABAP 字符串模版(String Template)核心知识点举例说明试读版
SAP ABAP 字符串模版(String Template)核心知识点举例说明试读版
27 0
|
8月前
|
存储 缓存 Java
【jvm系列-08】精通String字符串底层原理和运行机制(详解)
【jvm系列-08】精通String字符串底层原理和运行机制(详解)
149 0
|
存储 C++ 容器
C++ 第九节——map/set(用法+底层原理+模拟实现)
们需要知道的是,Map和Set的底层都是红黑树。
572 1
C++ 第九节——map/set(用法+底层原理+模拟实现)
|
存储 安全 Java
傻傻分不清楚?: 一文看懂Java中String的New操作和直接赋值字符串的区别
傻傻分不清楚?: 一文看懂Java中String的New操作和直接赋值字符串的区别
611 0
傻傻分不清楚?: 一文看懂Java中String的New操作和直接赋值字符串的区别
|
机器学习/深度学习 编译器 C++
【C++初阶】六、STL---string模拟实现
目录 一、模拟实现接口总览 二、string模拟实现 2.1 构造函数 2.2 析构函数 2.3 拷贝构造函数 2.3.1 传统写法 2.3.2 现代写法 2.4 赋值运算符重载 2.4.1 传统写法 2.4.2 现代写法 2.5 iterator 2.5.1 begin 2.6 Capacity 2.6.1 size 2.6.2 capacity 2.6.2 empty 2.6.3 reserve 2.6.4 resize 2.6.5 clear 2.7 Element access 2.7.1 operator[ ] 2.8 Modify 2.
55 0
【C++初阶】六、STL---string模拟实现
|
存储 C++
【C++要笑着学】深浅拷贝 | string 模拟实现 | 传统写法与现代写法(二)
本章将正式介绍深浅拷贝,在模拟实现 string 的同时带着去理解深浅拷贝。我们模拟实现 string类不是为了造更好的轮子,而是为了去学习它,理解它的本质!你自己造一次,心里会更清楚,也有利于加深对 string 的理解。
112 0
【C++要笑着学】深浅拷贝 | string 模拟实现 | 传统写法与现代写法(二)
|
测试技术 C语言 C++
【C++要笑着学】深浅拷贝 | string 模拟实现 | 传统写法与现代写法(一)
本章将正式介绍深浅拷贝,在模拟实现 string 的同时带着去理解深浅拷贝。我们模拟实现 string类不是为了造更好的轮子,而是为了去学习它,理解它的本质!你自己造一次,心里会更清楚,也有利于加深对 string 的理解。
54 0
【C++要笑着学】深浅拷贝 | string 模拟实现 | 传统写法与现代写法(一)
【C++要笑着学】深浅拷贝 | string 模拟实现 | 传统写法与现代写法(三)
本章将正式介绍深浅拷贝,在模拟实现 string 的同时带着去理解深浅拷贝。我们模拟实现 string类不是为了造更好的轮子,而是为了去学习它,理解它的本质!你自己造一次,心里会更清楚,也有利于加深对 string 的理解。
64 0
|
Java
java学习第九天笔记-方法176-构造方法代码分析-创建string对象的方式
java学习第九天笔记-方法176-构造方法代码分析-创建string对象的方式
72 0
java学习第九天笔记-方法176-构造方法代码分析-创建string对象的方式