key*_*rdP 13 c# oop string language-design contains
该String.Contains方法内部应该是这样
public bool Contains(string value)
{
return this.IndexOf(value, StringComparison.Ordinal) >= 0;
}
Run Code Online (Sandbox Code Playgroud)
IndexOf调用的重载看起来像这样
public int IndexOf(string value, StringComparison comparisonType)
{
return this.IndexOf(value, 0, this.Length, comparisonType);
}
Run Code Online (Sandbox Code Playgroud)
这里再次调用最终重载,然后CompareInfo.IndexOf使用签名调用相关方法
public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
Run Code Online (Sandbox Code Playgroud)
因此,调用最终的重载将是最快的(尽管在大多数情况下可以被认为是微优化).
我可能会遗漏一些明显的东西,但为什么该Contains方法不会直接调用最终的重载,因为在中间调用中没有进行其他工作,并且在两个阶段都可以获得相同的信息?
唯一的好处是,如果最终过载的签名发生变化,只需要进行一次更改(中间方法的更改),或者设计是否还有更多?
从评论中编辑(有关速度差异说明,请参阅更新2)
为了澄清的性能差异我得到的情况下,我犯了一个错误的地方:我跑这个基准测试(循环5次,以避免抖动偏差),并使用该扩展方法来比较的String.Contains方法
public static bool QuickContains(this string input, string value)
{
return input.IndexOf(value, 0, input.Length, StringComparison.OrdinalIgnoreCase) >= 0;
}
Run Code Online (Sandbox Code Playgroud)
循环看起来像这样
for (int i = 0; i < 1000000; i++)
{
bool containsStringRegEx = testString.QuickContains("STRING");
}
sw.Stop();
Console.WriteLine("QuickContains: " + sw.ElapsedMilliseconds);
Run Code Online (Sandbox Code Playgroud)
在基准测试中,QuickContains似乎比String.Contains我的机器快50%.
更新2(性能差异解释)
我在基准测试中发现了一些不公平的东西,这解释了很多.基准测试本身用于测量不区分大小写的字符串,但由于String.Contains只能执行区分大小写的搜索,ToUpper因此包含了该方法.这会使结果产生偏差,而不是最终输出,但至少String.Contains在非区分大小写的搜索中仅衡量性能.
所以现在,如果我使用这种扩展方法
public static bool QuickContains(this string input, string value)
{
return input.IndexOf(value, 0, input.Length, StringComparison.Ordinal) >= 0;
}
Run Code Online (Sandbox Code Playgroud)
使用StringComparison.Ordinal在2超载IndexOf呼叫和除去ToUpper,该QuickContains方法实际上变成最慢.IndexOf并且Contains在性能方面几乎相当.很明显,这就是ToUpper为什么在Contains和之间出现这种差异的结果IndexOf.
不确定为什么QuickContains扩展方法变得最慢.(可能与Contains具有[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]属性的事实相关?).
问题仍然存在,为什么不直接调用4重载方法,但似乎性能不受影响(如评论中Adrian和delnan在评论中指出的那样).
从我看过汇编以来已经有一段时间了(几年),我对MSIL和JIT几乎一无所知,所以这将是一个很好的练习 - 无法抗拒,所以这里只是一些,可能是多余的经验数据.IndexOf重载是否内联?
这是一个很小的控制台应用程序:
class Program
{
static void Main(string[] args)
{
"hello".Contains("hell");
}
}
Run Code Online (Sandbox Code Playgroud)
JIT在优化的Release版本中生成,Any CPU,以32位运行.我缩短了地址,删除了一些不相关的行:
--- ...\Program.cs
"hello".Contains("hell");
[snip]
17 mov ecx,dword ptr ds:[0320219Ch] ; pointer to "hello"
1d mov edx,dword ptr ds:[032021A0h] ; pointer to "hell"
23 cmp dword ptr [ecx],ecx
25 call 680A6A6C ; String.Contains()
[snip]
Run Code Online (Sandbox Code Playgroud)
将call在0x00000025放在这里:
String.Contains
00 push 0 ; startIndex = 0
02 push dword ptr [ecx+4] ; count = this.Length (second DWORD of String)
05 push 4 ; comparisonType = StringComparison.Ordinal
07 call FF9655A4 ; String.IndexOf()
0c test eax,eax
0e setge al ; if (... >= 0)
11 movzx eax,al
14 ret
Run Code Online (Sandbox Code Playgroud)
果然,它似乎直接调用String.IndexOf了四个参数的最终重载:三个pushed; 一个在edx(value:"地狱"); this("你好")in ecx.要确认,这是callat 0x00000005的位置:
00 push ebp
01 mov ebp,esp
03 push edi
04 push esi
05 push ebx
06 mov esi,ecx ; this ("hello")
08 mov edi,edx ; value ("hell")
0a mov ebx,dword ptr [ebp+10h]
0d test edi,edi ; if (value == null)
0f je 00A374D0
15 test ebx,ebx ; if (startIndex < 0)
17 jl 00A374FB
1d cmp dword ptr [esi+4],ebx ; if (startIndex > this.Length)
20 jl 00A374FB
26 cmp dword ptr [ebp+0Ch],0 ; if (count < 0)
2a jl 00A3753F
[snip]
Run Code Online (Sandbox Code Playgroud)
......这将是以下的主体:
public int IndexOf(string value,
int startIndex,
int count,
StringComparison comparisonType)
{
if (value == null)
throw new ArgumentNullException("value");
if (startIndex < 0 || startIndex > this.Length)
throw new ArgumentOutOfRangeException("startIndex",
Environment.GetResourceString("ArgumentOutOfRange_Index"));
if (count < 0 || startIndex > this.Length - count)
throw new ArgumentOutOfRangeException("count",
Environment.GetResourceString("ArgumentOutOfRange_Count"));
...
}
Run Code Online (Sandbox Code Playgroud)