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

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[] { 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,Intel Core i5处理器上运行的.NET 4.0,启用了“优化代码”编译的控制台项目。 但是,还启用了“抑制模块加载时的JIT优化”(请参阅​​接受的答案和注释)。

在完全启用优化的情况下, Length比删除委托和其他开销的IsNullOrEmpty快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 

这是因为您在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 

并且对于长度== 0

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

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

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

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

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

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

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

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

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

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

我相信你的测试不正确:

此测试显示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[] { 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

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

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

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

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

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

和平!

  • :编辑