为什么String.IsNullOrEmpty比String.Length快?

时间:2012-04-28 03:39:57

标签: c# .net string performance is-empty

ILSpy表明String.IsNullOrEmpty是根据String.Length实施的。但是,为什么String.IsNullOrEmpty(s)s.Length == 0更快?

例如,它在此基准测试中的速度提高了5%:

var stopwatches = Enumerable.Range(0, 4).Select(_ => new Stopwatch()).ToArray();
var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,STU,V,W,X,Y,Z,".Split(',');
var testers = new Func<string, bool>[] { s => s == String.Empty, s => s.Length == 0, s => String.IsNullOrEmpty(s), s => s == "" };
int count = 0;
for (int i = 0; i < 10000; ++i) {
    stopwatches[i % 4].Start();
    for (int j = 0; j < 1000; ++j)
        count += strings.Count(testers[i % 4]);
    stopwatches[i % 4].Stop();
}

(其他基准测试显示了类似的结果。这一点最大限度地减少了在我的计算机上运行的影响。另外,与空字符串相比,测试结果与IsNullOrEmpty相比慢了大约13%。 )

此外,为什么IsNullOrEmpty仅在x86上更快,而在x64上String.Length的速度提高约9%?

更新:测试设置详细信息:在64位Windows 7上运行的.NET 4.0,Intel Core i5处理器,启用了“优化代码”编译的控制台项目。但是,还启用了“抑制模块加载时的JIT优化”(参见接受的答案和注释)。

完全启用优化后,LengthIsNullOrEmpty快约14%,代理和其他开销被移除,如此测试中所示:

var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,,STU,V,,W,,X,,,Y,,Z,".Split(',');
int count = 0;
for (uint i = 0; i < 100000000; ++i)
    count += strings[i % 32].Length == 0 ? 1 : 0; // Replace Length test with String.IsNullOrEmpty

7 个答案:

答案 0 :(得分:22)

这是因为您在Visual Studio中运行了基准测试,这阻止了JIT编译器优化代码。如果没有优化,则会为 String.IsNullOrEmpty

生成此代码
00000000   push        ebp 
00000001   mov         ebp,esp 
00000003   sub         esp,8 
00000006   mov         dword ptr [ebp-8],ecx 
00000009   cmp         dword ptr ds:[00153144h],0 
00000010   je          00000017 
00000012   call        64D85BDF 
00000017   mov         ecx,dword ptr [ebp-8] 
0000001a   call        63EF7C0C 
0000001f   mov         dword ptr [ebp-4],eax 
00000022   movzx       eax,byte ptr [ebp-4] 
00000026   mov         esp,ebp 
00000028   pop         ebp 
00000029   ret 

现在将其与为 Length == 0

生成的代码进行比较
00000000   push   ebp 
00000001   mov    ebp,esp 
00000003   sub    esp,8 
00000006   mov    dword ptr [ebp-8],ecx 
00000009   cmp    dword ptr ds:[001E3144h],0 
00000010   je     00000017 
00000012   call   64C95BDF 
00000017   mov    ecx,dword ptr [ebp-8] 
0000001a   cmp    dword ptr [ecx],ecx 
0000001c   call   64EAA65B 
00000021   mov    dword ptr [ebp-4],eax 
00000024   cmp    dword ptr [ebp-4],0 
00000028   sete   al 
0000002b   movzx  eax,al 
0000002e   mov    esp,ebp 
00000030   pop    ebp 
00000031   ret 

你可以看到, Length == 0 的代码完成了为 String.IsNullOrEmpty 编写代码的所有代码,但另外它尝试了类似于愚蠢地转换boolean值的东西(返回从长度比较)再次到布尔值,这使得它比 String.IsNullOrEmpty 慢。

如果编译启用了优化的程序(发布模式)并直接从Windows运行.exe文件,则JIT编译器生成的代码要好得多。对于 String.IsNullOrEmpty ,它是:

001f0650   push    ebp
001f0651   mov     ebp,esp
001f0653   test    ecx,ecx
001f0655   je      001f0663
001f0657   cmp     dword ptr [ecx+4],0
001f065b   sete    al
001f065e   movzx   eax,al
001f0661   jmp     001f0668
001f0663   mov     eax,1
001f0668   and     eax,0FFh
001f066d   pop     ebp
001f066e   ret

长度== 0

001406f0   cmp     dword ptr [ecx+4],0
001406f4   sete    al
001406f7   movzx   eax,al
001406fa   ret

使用此代码,结果符合预期,即 Length == 0 略快于 String.IsNullOrEmpty

值得一提的是,在你的基准测试中使用Linq,lambda表达式和计算模数并不是一个好主意,因为这些操作很慢(相对于字符串比较)并且使得基准测试的结果不准确。

答案 1 :(得分:4)

您的基准测试不会测量String.IsNullOrEmpty和String.Length,而是测量为函数生成不同的lambda表达式。即仅仅包含单个函数调用(IsNullOrEmpty)的委托比具有函数调用和比较(长度== 0)的委托更快,这并不奇怪。

比较实际调用 - 编写代码,直接调用它们而不需要代理。

编辑:我的粗略测量表明,使用IsNullOrEmpty的委托版本比其余部分快一些,而直接调用相同的比较则是在我的机器上以相反的顺序(由于显着减少的额外代码数量大约快两倍)。结果可能在机器,x86 / x64模式以及运行时版本之间保持警惕。出于实际目的,如果您需要在LINQ查询中使用它们,我会认为所有4种方法大致相同。

总的来说,我怀疑在这些方法之间选择的实际程序会有可衡量的差异,所以选择一个最易读的并使用它的方法。我通常更喜欢IsNullOrEmpty,因为它在条件下给出错误的机会得到== /!=错误。

从时间关键代码中完全删除字符串操作将会带来更高的好处,即在这些选择之间进行选择,同时为关键代码删除LINQ也是一种选择。一如既往 - 确保在现实生活中测量整体程序速度。

答案 2 :(得分:1)

你的测试是错误的。根据定义,IsNullOrEmpty不能更快,因为它会进行额外的空比较操作,然后测试长度。

所以答案可能是:因为你的测试,它会更快。但是,即使您的代码显示在x86和x64模式下,我的机器上的IsNullOrEmpty也一直较慢。

答案 3 :(得分:1)

我相信你的测试不正确:

此测试显示string.IsNullOrEmpty总是慢于s.Length==0,因为它会执行额外的空检查:

var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,STU,V,W,X,Y,Z,".Split(',');
var testers = new Func<string, bool>[] { 
    s => s == String.Empty, 
    s => s.Length == 0, 
    s => String.IsNullOrEmpty(s), 
    s => s == "" ,
};
int n = testers.Length;
var stopwatches = Enumerable.Range(0, testers.Length).Select(_ => new Stopwatch()).ToArray();
int count = 0;
for(int i = 0; i < n; ++i) { // iterate testers one by one
    Stopwatch sw = stopwatches[i];
    var tester = testers[i];
    sw.Start();
    for(int j = 0; j < 10000000; ++j) // increase this count for better precision
        count += strings.Count(tester);
    sw.Stop();
}
for(int i = 0; i < testers.Length; i++)
    Console.WriteLine(stopwatches[i].ElapsedMilliseconds);

结果:

6573
5328
5488
6419

当您确保目标数据不包含空字符串时,可以使用s.Length==0。在其他情况下,我建议您使用String.IsNullOrEmpty

答案 4 :(得分:0)

我认为IsNullOrEmpty不可能更快,因为所有其他人都表示它也会检查是否为空。但是更快或者差别不是很小,这使得使用IsNullOrEmpty只是因为这个额外的空值检查使您的代码更安全。

答案 5 :(得分:-2)

CLR via CSharp第10章“属性”中,Jeff Richter写道:

  

属性方法可能需要很长时间才能执行;现场访问总是立即完成。使用属性的一个常见原因是执行线程同步,这可以永久停止线程,因此,如果需要线程同步,则不应使用属性。在那种情况下,一种方法是优选的。此外,如果可以远程访问您的类(例如,您的类派生自System.MarshalByRefObject),则调用属性方法将非常慢,因此,某个方法优先于属性。在我看来,派生自MarshalByRefObject的类不应该使用属性。

因此,如果我们看到String.Length是属性,String.IsNullOrEmpty是一个可能比属性String.Length执行得更快的方法。

答案 6 :(得分:-4)

它可能是由涉及的变量的类型引起的。 * Empty似乎使用布尔值,长度为int(我猜)。

和平!

  • :编辑