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

Edw*_*rey 19 .net c# 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();
}
Run Code Online (Sandbox Code Playgroud)

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

另外,为什么IsNullOrEmptyx86上的速度更快,而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
Run Code Online (Sandbox Code Playgroud)

Ňuf*_*Ňuf 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 
Run Code Online (Sandbox Code Playgroud)

现在将它与为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 
Run Code Online (Sandbox Code Playgroud)

你可以看到,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
Run Code Online (Sandbox Code Playgroud)

并且对于长度== 0:

001406f0   cmp     dword ptr [ecx+4],0
001406f4   sete    al
001406f7   movzx   eax,al
001406fa   ret
Run Code Online (Sandbox Code Playgroud)

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

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