2012-04-28 76 views
18

ILSpy显示String.IsNullOrEmpty按照String.Length执行。但为什么String.IsNullOrEmpty(s)s.Length == 0快?为什么String.IsNullOrEmpty比String.Length更快?

例如,它的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,英特尔酷睿i5处理器,启用“优化代码”编译的控制台项目上运行的.NET 4.0。但是,“禁止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

不知道你所处的确切情况,我相当确信在大多数情况下,还有其他的优化方法让人痛苦,而不是最快的方法来检查一个字符串是否没有任何数据。 – Andrew 2012-04-28 03:41:52

+0

@minitech因为有4个测试人员,他独立计时。 – 2012-04-28 03:46:43

+0

'Empty'和'=='与'Length'和'IsNullOrEmpty'相比如何? – 2012-04-28 03:47:40

回答

20

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

,现在把它比作代码生成的长度== 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 

可以看到,对于长度代码== 0做的一切代码为String.IsNullOrEmpty,但此外它会尝试类似愚蠢地转换布尔值(从长度比较返回)aga in to boolean,这使得它比String.IsNullOrEmpty慢。

如果您在启用优化(发布模式)的情况下编译程序并直接从Windows运行.exe文件,那么由JIT编译器生成的代码要好得多。对于字符串。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 

有了这个代码,结果,即长度== 0略快字符串.IsNullOrEmpty

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

+0

无论从内部运行测试,我都会得到相同的结果我在Visual Studio内部或外部运行测试。在这两种情况下,我都在发布模式下针对.NET Framework 4进行构建,并且在项目文件(默认设置)中启用了“优化代码”设置。在Visual Studio中,我确实看到了您发布的未优化的汇编代码。如何查看在Visual Studio外部运行时生成的汇编代码? – 2012-04-28 11:25:31

+0

这很奇怪。我使用不同的CPU在不同的操作系统(Windows Server 2008 x64,Windows XP x86)的3台不同的计算机上测试了这个基准测试,我总是得到Length == 0的速度更快。此外,我关闭了Visual Studio中用于此项目的.PDB文件的生成,但这可能不是问题。你试过另一台电脑吗?我使用[WinDbg](http://archive.msdn.microsoft.com/debugtoolswindows)连接到运行进程以查看优化的汇编代码。 – 2012-04-28 12:42:24

+0

我重新进行了VS2010内部VS外部的实验,并且无法再现我在之前的评论中报告的内容。现在我看到了和你一样的感觉,VS2010之外的“长度”要快一点。我还记得这个设置:工具>选项>调试>常规>在模块加载时抑制JIT优化。我忘记了关闭它。当我这样做时,我在VS2010中得到了相同的结果,“长度”更快。此外,通过切换JIT优化设置,我可以在VS2010中重现所有4个汇编代码清单。 – 2012-04-28 14:15:24

-4

它可以通过类型所涉及的变量引起的。 *空似乎使用布尔值,长度为int(我猜)。

和平!

  • :编辑
+3

-1用于猜测。并猜测不正确。虽然你肯定会被鼓励在这个论坛上分享你的知识,但在猜测答案时会给我们的话语添加不希望的噪音。 – 2012-05-23 18:15:35

1

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

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

+0

我相信IsNullOrEmpty可以在空字符串的情况下更快地执行,因为不执行长度检查。虽然我怀疑任何可观的性能提升都会被观察到,但如果字符串经常被认为是无效的,这种检查可能会更有意义。 – overslacked 2012-04-28 05:03:09

+1

我相信这是不合适的,因为'.Length'在这种情况下根本不适用,因此请讨论'null'字符串的情况:) – 2012-04-28 05:10:27

+0

Touché! 。 。 。 。 – overslacked 2012-04-28 05:29:04

4

您的基准测试不测量String.IsNullOrEmpty vs String.Length,而是测试函数如何生成不同的lambda表达式。即代表只包含单个函数调用(IsNullOrEmpty)的代表比使用函数调用和比较(长度== 0)的代码更快并不令人惊讶。

为了比较直接调用它们而不委托代理的真正的调用 - 写入代码。

编辑:我粗略的测量表明,委托版本与IsNullOrEmpty稍快,其余,而直接调用相同的比较是在我的机器上以相反的顺序(因为额外的代码数量少得多) 。结果可能在机器,x86/x64模式之间以及运行时版本之间谨慎。出于实际的目的,我认为如果你需要在LINQ查询中使用它们,所有4种方式大致相同。

总的来说,我怀疑这些方法之间的选择会导致实际程序中出现可衡量的差异,因此请选择最适合您的程序并使用它。我通常更喜欢IsNullOrEmpty,因为它在条件中获得== /!=错误的机会较少。

从时间关键代码中完全去除字符串操作会带来更多的选择这些选择的更高利益,同时也会丢弃关键代码的LINQ是一种选择。一如既往 - 确保在实际生活场景中测量整体程序速度。

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

+0

当我这样构造测试时,得到的结果相同,但测试之间的标准差更高。我认为这是因为其他流程或操作系统代码更容易影响单个测试人员的不公平。平均而言,我最终还是希望x86上的'IsNullOrEmpty'更快。我在64位Core i5上运行。你是否一贯发现“长度”更快? – 2012-04-28 11:02:22

0

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

-2

CLR via CSharp第10章“属性”杰夫里氏写道:

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

因此,如果我们看到String.Length是财产和String.IsNullOrEmpty是可以执行比财产String.Length更快的方法。

+1

属性仅作为元数据存在。当你“获取”这个属性时,它调用一个名为'get_PropertyName'的常规方法,当你设置它的属性时,它会调用一个名为'set_PropertyName'的常规方法。从JIT和执行时间的角度来看,属性和方法没有区别。 – 2012-06-23 22:24:47