23

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();
}

(其他基准测试显示类似的结果。这个最小化了 cruft 在我的计算机上运行的影响。此外,与空字符串比较的测试结果相同,比 . 慢约 13% IsNullOrEmpty。)

此外,为什么IsNullOrEmpty只在 x86 上更快,而在 x64 上String.Length却快 9%?

更新:测试设置详细信息:.NET 4.0 在 64 位 Windows 7、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
4

7 回答 7

23

这是因为您在 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的所有代码,但另外它尝试将布尔值(从长度比较返回)再次愚蠢地转换为布尔值,这使得它比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

对于Length == 0

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

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

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

于 2012-04-28T09:45:56.283 回答
4

您的基准测试不衡量 String.IsNullOrEmpty 与 String.Length,而是衡量不同的 lambda 表达式如何生成函数。也就是说,仅包含单个函数调用 (IsNullOrEmpty) 的委托比具有函数调用和比较 (长度 == 0) 的委托要快并不奇怪。

要比较实际调用 - 编写无需委托直接调用它们的代码。

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

总的来说,我怀疑这些方法之间的选择是否会在实际程序中存在可衡量的差异,因此请选择对您来说最易读的一种并使用它。我通常更喜欢 IsNullOrEmpty,因为它在某种情况下获得 ==/!= 错误的机会更少。

从时间关键代码中完全删除字符串操作将可能带来更大的好处,即在这些选择之间进行选择,也可以选择为关键代码放弃 LINQ。与往常一样 - 确保在现实生活场景中测量整体程序速度。

于 2012-04-28T04:53:28.760 回答
1

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

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

于 2012-04-28T04:52:45.563 回答
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.

于 2012-04-28T05:01:53.593 回答
0

我认为不可能IsNullOrEmpty更快,因为正如所有其他人所说,它也会检查空值。但无论快不快,差异都会变得如此之小,这给使用带来了IsNullOrEmpty好处,因为这个额外的空检查使您的代码更安全。

于 2012-04-28T05:30:27.160 回答
-2

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

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

因此,如果我们看到String.Lengthis property 并且String.IsNullOrEmptyis 一个可能比 property 执行得更快的方法String.Length

于 2012-04-28T06:00:43.890 回答
-4

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

和平 !

  • : 编辑
于 2012-04-28T03:44:29.920 回答