为什么不';t字符串.包含直接调用最终重载
本文关键字:调用 重载 包含直 字符串 为什么不 | 更新日期: 2023-09-27 18:21:57
String.Contains方法在内部中如下所示
public bool Contains(string value)
{
return this.IndexOf(value, StringComparison.Ordinal) >= 0;
}
被调用的IndexOf
过载看起来像这个
public int IndexOf(string value, StringComparison comparisonType)
{
return this.IndexOf(value, 0, this.Length, comparisonType);
}
在这里,对最终重载进行另一个调用,然后调用具有签名的相关CompareInfo.IndexOf
方法
public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
因此,调用最终过载将是最快的(尽管在大多数情况下可能被视为微观优化)。
我可能遗漏了一些明显的内容,但考虑到在中间调用中没有完成其他工作,并且在两个阶段都有相同的信息,为什么Contains
方法不直接调用最终重载?
唯一的优点是,如果最终重载的签名发生变化,只需要进行一次更改(中间方法的更改),还是设计中还有更多的更改?
从注释中编辑(请参阅更新2了解速度差异的解释)
为了澄清我的表现差异,以防我在某个地方犯了错误:我运行了这个基准测试(循环5次以避免抖动偏差),并使用这个扩展方法与String.Contains
方法进行比较
public static bool QuickContains(this string input, string value)
{
return input.IndexOf(value, 0, input.Length, StringComparison.OrdinalIgnoreCase) >= 0;
}
环路看起来像这个
for (int i = 0; i < 1000000; i++)
{
bool containsStringRegEx = testString.QuickContains("STRING");
}
sw.Stop();
Console.WriteLine("QuickContains: " + sw.ElapsedMilliseconds);
在基准测试中,在我的机器上,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;
}
在2重载IndexOf
调用中使用StringComparison.Ordinal
并移除ToUpper
,QuickContains
方法实际上变得最慢。CCD_ 14和CCD_。很明显,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");
}
}
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]
0x00000025处的call
位于此处:
字符串。包含
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
果不其然,它似乎直接调用了带有四个参数的最终String.IndexOf
重载:三个push
ed;一个在edx
(value
:"地狱");ecx
中的this
("你好")。为了确认,这是0x00000005处的call
的去向:
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]
这将是的主体
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"));
...
}