由浅入深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是否为空的方法,也有两种判断值是否相等的方法。下面我们来看看:
class Program { static void Main(string[] args) { //判断字符串是否为空 string str1 = "MyWord"; if (str1 == "") ; if (str1 == string.Empty) ; if (str1 != null && str1.Length== 0) ; if (string.IsNullOrEmpty(str1)) ;
} }
二、下面我们看看上面的Cs代码生成的CIL代码如下: .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 85 (0x55) .maxstack 2 //声明3个参数,分别是str1和bool值 .locals init ([0] string str1, [1] bool CS$4$0000) IL_0000: nop //推送对元数据中存储的"MyWord"字符串的新对象引用 IL_0001: ldstr "MyWord" //将"MyWord"压栈到参数0 IL_0006: stloc.0
//--------string空判断第一种方法 if (str1 == "") -------- //将"MyWord"从参数0处加载到计算堆栈上 IL_0007: ldloc.0 //推送对元数据中存储的""字符串的新对象引用 IL_0008: ldstr "" //通过System.String::op_Equality函数判断是否相等 IL_000d: call bool [mscorlib]System.String::op_Equality(string, string) //将整数值 0 作为 int32 推送到计算堆栈上 IL_0012: ldc.i4.0 //ceq比较两个值。如果这两个值相等,则将整数值 1 (int32)推送到计算堆栈上; //否则,将 0 (int32) 推送到计算堆栈上。 IL_0013: ceq //将true或者false的bool值弹出栈存到参数1去 IL_0015: stloc.1 //从参数1中加载数据到计算堆栈上去 IL_0016: ldloc.1 //如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。 //也就是if判断中如果结果为true的话,则运行内部代码 IL_0017: brtrue.s IL_0019
//--------string空判断第二种方法 if (str1 == string.Empty) -------- IL_0019: ldloc.0 //Ldsfld 将静态字段的值推送到计算堆栈上。 IL_001a: ldsfld string [mscorlib]System.String::Empty IL_001f: call bool [mscorlib]System.String::op_Equality(string, string) IL_0024: ldc.i4.0 IL_0025: ceq IL_0027: stloc.1 IL_0028: ldloc.1 IL_0029: brtrue.s IL_002b
//--------string空判断第三种方法 if (str1!=null&&str1.Length == 0) -------- IL_002b: ldloc.0 //对象调用后期绑定方法,并且将返回值推送到计算堆栈上。<==> str1!=null IL_002c: brfalse.s IL_003c IL_002e: ldloc.0 //调用系统函数获取长度 IL_002f: callvirt instance int32 [mscorlib]System.String::get_Length() IL_0034: ldc.i4.0 IL_0035: ceq IL_0037: ldc.i4.0 IL_0038: ceq IL_003a: br.s IL_003d IL_003c: ldc.i4.1 IL_003d: stloc.1 IL_003e: ldloc.1 IL_003f: brtrue.s IL_0041
//--------string空判断第四种方法 if (string.IsNullOrEmpty(str1)) -------- IL_0041: ldloc.0 //直接调用系统System.String::IsNullOrEmpty(string)函数比对 IL_0042: call bool [mscorlib]System.String::IsNullOrEmpty(string) IL_0047: ldc.i4.0 IL_0048: ceq IL_004a: stloc.1 IL_004b: ldloc.1 IL_004c: brtrue.s IL_004e } // end of method Program::Main
4种方法的CIL分析
A.if (str1 == ""),在这里我们需要新构造一个""空字符,然后再调用System.String::op_Equality(string,string)函数对str1和空字符进行对比。
//--------string空判断第一种方法 if (str1 == "") -------- //将"MyWord"从参数0处加载到计算堆栈上 IL_0007: ldloc.0 //推送对元数据中存储的""字符串的新对象引用 IL_0008: ldstr "" //通过System.String::op_Equality函数判断是否相等 IL_000d: call bool [mscorlib]System.String::op_Equality(string, string) //将整数值 0 作为 int32 推送到计算堆栈上 IL_0012: ldc.i4.0 //ceq比较两个值。如果这两个值相等,则将整数值 1 (int32)推送到计算堆栈上; //否则,将 0 (int32) 推送到计算堆栈上。 IL_0013: ceq //将true或者false的bool值弹出栈存到参数1去 IL_0015: stloc.1 //从参数1中加载数据到计算堆栈上去 IL_0016: ldloc.1 //如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。 //也就是if判断中如果结果为true的话,则运行内部代码 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),以确定是否为空。 //--------string空判断第二种方法 if (str1 == string.Empty) -------- IL_0019: ldloc.0 //Ldsfld 将静态字段的值推送到计算堆栈上。 IL_001a: ldsfld string [mscorlib]System.String::Empty IL_001f: call bool [mscorlib]System.String::op_Equality(string, string) IL_0024: ldc.i4.0 IL_0025: ceq IL_0027: stloc.1 IL_0028: ldloc.1 IL_0029: brtrue.s IL_002b
C.if (str1.Length == 0),在这里我们调用[mscorlib]System.String::get_Length()函数获取到字符串长度,然后这个长度和0相对比 //--------string空判断第三种方法 if (str1!=null&&str1.Length == 0) -------- IL_002b: ldloc.0 //对象调用后期绑定方法,并且将返回值推送到计算堆栈上。<==> str1!=null IL_002c: brfalse.s IL_003c IL_002e: ldloc.0 //调用系统函数获取长度 IL_002f: callvirt instance int32 [mscorlib]System.String::get_Length() IL_0034: ldc.i4.0 IL_0035: ceq IL_0037: ldc.i4.0 IL_0038: ceq IL_003a: br.s IL_003d IL_003c: ldc.i4.1 IL_003d: stloc.1 IL_003e: ldloc.1 IL_003f: brtrue.s IL_0041
D.if (string.IsNullOrEmpty(str1)),这种方式直接调用系统的System.String::IsNullOrEmpty(string)函数直接比对出结果。
//--------string空判断第四种方法 if (string.IsNullOrEmpty(str1)) -------- IL_0041: ldloc.0 //直接调用系统System.String::IsNullOrEmpty(string)函数比对 IL_0042: call bool [mscorlib]System.String::IsNullOrEmpty(string) IL_0047: ldc.i4.0 IL_0048: ceq IL_004a: stloc.1 IL_004b: ldloc.1 IL_004c: brtrue.s IL_004e

性能分析
下面我们通过using System.Diagnostics;命名空间下的Stopwatch对象来计算这4种调用方式所消耗的大概时间。
请看cs代码如下:
class Program { static void Main(string[] args) { //判断字符串是否为空 string str1 = "MyWord"; //第一种方法耗时计算 Stopwatch sw1 = new Stopwatch(); sw1.Start(); if (str1 == "") ; sw1.Stop(); //第二种方法耗时计算 Stopwatch sw2 = new Stopwatch(); sw2.Start(); if (str1 == string.Empty) ; sw2.Stop(); //第三种方法耗时计算 Stopwatch sw3 = new Stopwatch(); sw3.Start(); if (str1!=null&&str1.Length == 0) ; sw3.Stop(); //第四种方法耗时计算 Stopwatch sw4 = new Stopwatch(); sw4.Start(); if (string.IsNullOrEmpty(str1)) ; sw4.Stop(); Console.WriteLine(@"if (str1 == "")的判断时间是:" + sw1.Elapsed); Console.WriteLine(@"if (str1 == string.Empty)的判断时间是:" + sw2.Elapsed); Console.WriteLine(@"if (str1!=null&&str1.Length == 0)的判断时间是:" + sw3.Elapsed); Console.WriteLine(@"if (string.IsNullOrEmpty(str1)) 的判断时间是:" + sw4.Elapsed); Console.ReadLine(); }
然后我们需要看看结果如何,为了提高精确度,我们运行多次结果,然后就知道哪种方式的效率最高。
下面我们来看在我的电脑上的运行时间情况如下面的图所示:
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
然后我将这段代码发我一个朋友那里得到的运行情况如下图所示:
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?
鉴于时间跨度太小,以及各种运行环境的不同,还有其他一些原因,对于结果和答案都有有所影响,所以上面的运行结果仅做参考。大家也可以将这段测试代码在自己的电脑上运行一下,看看究竟结果如何?
思考:这4种方法的效率究竟谁高谁低?应该如何排序?为什么形成这样的差异?

扩展阅读
I.1第一种方法和第二种方法都会使用到一个System.String::op_Equality(string,string)方法,这个方法的CIL代码我们使用ILDASM查看mscorlib.dll文件即可:
由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?由浅入深CIL系列:5.抛砖引玉:判断string是否为空的四种方法的CIL代码看看效率如何?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) .method public hidebysig static bool Equals(string a, string b) cil managed { // 代码大小 22 (0x16) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: bne.un.s IL_0006 IL_0004: ldc.i4.1 IL_0005: ret IL_0006: ldarg.0 IL_0007: brfalse.s IL_000c IL_0009: ldarg.1 IL_000a: brtrue.s IL_000e IL_000c: ldc.i4.0 IL_000d: ret IL_000e: ldarg.0 IL_000f: ldarg.1 IL_0010: call bool System.String::EqualsHelper(string, string) IL_0015: ret } // end of method String::Equals

I.3上面这段IL代码内部调用了bool System.String::EqualsHelper(string, string) 方法,这个方法的CIL代码如下,其内部调用了多次int32 System.String::get_Length()函数:
System.String::EqualsHelper(string,string) .method private hidebysig static bool EqualsHelper(string strA, string strB) cil managed { .custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency, valuetype System.Runtime.ConstrainedExecution.Cer) = ( 01 00 03 00 00 00 01 00 00 00 00 00 ) // 代码大小 199 (0xc7) .maxstack 3 .locals init (int32 V_0, char& pinned V_1, char& pinned V_2, char* V_3, char* V_4, bool V_5) IL_0000: ldarg.0 IL_0001: callvirt instance int32 System.String::get_Length() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldarg.1 IL_0009: callvirt instance int32 System.String::get_Length() IL_000e: beq.s IL_0012 IL_0010: ldc.i4.0 IL_0011: ret IL_0012: ldarg.0 IL_0013: ldflda char System.String::m_firstChar IL_0018: stloc.1 IL_0019: ldarg.1 IL_001a: ldflda char System.String::m_firstChar IL_001f: stloc.2 IL_0020: ldloc.1 IL_0021: conv.i IL_0022: stloc.3 IL_0023: ldloc.2 IL_0024: conv.i IL_0025: stloc.s V_4 IL_0027: br.s IL_0097 IL_0029: ldloc.3 IL_002a: ldind.i4 IL_002b: ldloc.s V_4 IL_002d: ldind.i4 IL_002e: beq.s IL_0038 IL_0030: ldc.i4.0 IL_0031: stloc.s V_5 IL_0033: leave IL_00c4 IL_0038: ldloc.3 IL_0039: ldc.i4.4 IL_003a: conv.i IL_003b: add IL_003c: ldind.i4 IL_003d: ldloc.s V_4 IL_003f: ldc.i4.4 IL_0040: conv.i IL_0041: add IL_0042: ldind.i4 IL_0043: beq.s IL_004a IL_0045: ldc.i4.0 IL_0046: stloc.s V_5 IL_0048: leave.s IL_00c4 IL_004a: ldloc.3 IL_004b: ldc.i4.8 IL_004c: conv.i IL_004d: add IL_004e: ldind.i4 IL_004f: ldloc.s V_4 IL_0051: ldc.i4.8 IL_0052: conv.i IL_0053: add IL_0054: ldind.i4 IL_0055: beq.s IL_005c IL_0057: ldc.i4.0 IL_0058: stloc.s V_5 IL_005a: leave.s IL_00c4 IL_005c: ldloc.3 IL_005d: ldc.i4.s 12 IL_005f: conv.i IL_0060: add IL_0061: ldind.i4 IL_0062: ldloc.s V_4 IL_0064: ldc.i4.s 12 IL_0066: conv.i IL_0067: add IL_0068: ldind.i4 IL_0069: beq.s IL_0070 IL_006b: ldc.i4.0 IL_006c: stloc.s V_5 IL_006e: leave.s IL_00c4 IL_0070: ldloc.3 IL_0071: ldc.i4.s 16 IL_0073: conv.i IL_0074: add IL_0075: ldind.i4 IL_0076: ldloc.s V_4 IL_0078: ldc.i4.s 16 IL_007a: conv.i IL_007b: add IL_007c: ldind.i4 IL_007d: beq.s IL_0084 IL_007f: ldc.i4.0 IL_0080: stloc.s V_5 IL_0082: leave.s IL_00c4 IL_0084: ldloc.3 IL_0085: ldc.i4.s 20 IL_0087: conv.i IL_0088: add IL_0089: stloc.3 IL_008a: ldloc.s V_4 IL_008c: ldc.i4.s 20 IL_008e: conv.i IL_008f: add IL_0090: stloc.s V_4 IL_0092: ldloc.0 IL_0093: ldc.i4.s 10 IL_0095: sub IL_0096: stloc.0 IL_0097: ldloc.0 IL_0098: ldc.i4.s 10 IL_009a: bge.s IL_0029 IL_009c: br.s IL_00b5 IL_009e: ldloc.3 IL_009f: ldind.i4 IL_00a0: ldloc.s V_4 IL_00a2: ldind.i4 IL_00a3: bne.un.s IL_00b9 IL_00a5: ldloc.3 IL_00a6: ldc.i4.4 IL_00a7: conv.i IL_00a8: add IL_00a9: stloc.3 IL_00aa: ldloc.s V_4 IL_00ac: ldc.i4.4 IL_00ad: conv.i IL_00ae: add IL_00af: stloc.s V_4 IL_00b1: ldloc.0 IL_00b2: ldc.i4.2 IL_00b3: sub IL_00b4: stloc.0 IL_00b5: ldloc.0 IL_00b6: ldc.i4.0 IL_00b7: bgt.s IL_009e IL_00b9: ldloc.0 IL_00ba: ldc.i4.0 IL_00bb: cgt IL_00bd: ldc.i4.0 IL_00be: ceq IL_00c0: stloc.s V_5 IL_00c2: leave.s IL_00c4 IL_00c4: ldloc.s V_5 IL_00c6: ret } // 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) .method public hidebysig static bool IsNullOrEmpty(string 'value') cil managed { // 代码大小 15 (0xf) .maxstack 8 IL_0000: ldarg.0 IL_0001: brfalse.s IL_000d IL_0003: ldarg.0 IL_0004: callvirt instance int32 System.String::get_Length() IL_0009: ldc.i4.0 IL_000a: ceq IL_000c: ret IL_000d: ldc.i4.1 IL_000e: ret } // end of method String::IsNullOrEmpty


Tags: 

延伸阅读

最新评论

发表评论