C# 为什么';包含直接调用最终重载吗?
该方法在内部看起来是这样的C# 为什么';包含直接调用最终重载吗?,c#,string,oop,language-design,contains,C#,String,Oop,Language Design,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,
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)
因此,调用最终重载将是最快的(尽管在大多数情况下可能被视为微观优化)
我可能遗漏了一些明显的东西,但是为什么包含方法不直接调用最终重载,因为在中间调用中没有完成其他工作,而且在两个阶段都有相同的信息
如果最终重载的签名发生更改,那么只需要进行一次更改(即中间方法的更改),这是唯一的优势,还是设计中还有更多的更改
根据评论进行编辑(有关速度差异的解释,请参阅更新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);
使用StringComparison.Ordinal
在调用和删除ToUpper
的2重载索引中,QuickContains
方法实际上变得最慢<代码> <代码> >和<代码>包含在性能方面基本上是一致的。显然是ToUpper
调用扭曲了Contains
和IndexOf
之间的差异
不确定为什么QuickContains
扩展方法变得最慢。(可能与包含具有[\uu DynamicallyInvokable,TargetedPatchingOptOut(“跨NGen图像边界内联的性能关键”)]
属性有关)
关于为什么不直接调用4重载方法的问题仍然存在,但这似乎不会影响性能(正如Adrian和delnan在评论中指出的那样)。我已经有一段时间(几年)没有研究汇编了,我对MSIL和JIT几乎一无所知,所以这将是一个很好的练习-无法抗拒,这里有一些可能是多余的经验数据。IndexOf
重载是否内联
这里有一个小小的控制台应用程序:
class Program
{
static void Main(string[] args)
{
"hello".Contains("hell");
}
}
JIT在一个优化的发布版本中生成这个,任何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处的调用
如下:
字符串。包含
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
中的(“你好”)。要确认,这就是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]
00推送ebp
01 mov ebp,esp
03推式电子数据交换
04推动esi
05推动ebx
06电影esi、ecx;这个(“你好”)
08 mov edi,edx;价值(“地狱”)
0a mov ebx,dword ptr[ebp+10h]
0d测试edi,edi;如果(值==null)
0f je 00A374D0
15试验ebx,ebx;如果(起始索引<0)
17 jl 00A374FB
1d cmp dword ptr[esi+4],ebx;如果(startIndex>this.Length)
20 jl 00A374FB
26 cmp dword ptr[ebp+0Ch],0;如果(计数<0)
2a jl 00A3753F
[剪报]
。。。这将是以下内容的主体:
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"));
...
}
public int IndexOf(字符串值,
int startIndex,
整数计数,
StringComparison(比较类型)
{
如果(值==null)
抛出新的ArgumentNullException(“值”);
if(startIndex<0 | | startIndex>this.Length)
抛出新ArgumentOutOfRangeException(“startIndex”,
GetResourceString(“ArgumentOutOfRange_索引”);
if(count<0 | | startIndex>this.Length-count)
抛出新ArgumentOutOfRangeException(“计数”,
GetResourceString(“ArgumentOutOfRange_Count”);
...
}
方法调用可能由编译器内联,因此当代码开始运行时,String.Contains(String value)
的任何出现都可能已经被重写为更复杂的版本(留下的唯一原因是您提到的优点,而不是缺点)。然而,这显然只是猜测。这是个好主意。我刚刚尝试通过创建一个自定义的Contains
方法来对其进行基准测试,该方法直接调用最终重载,结果比现有的Contains
方法快50%,因此性能受到了明显的影响(尽管绝对值很小;1米循环中有200毫秒)。@keyboardP我觉得这有点难以置信;即使是最简单的内联启发式和最简单的内联,也应该使这两种变体难以区分。你确定你没有提交吗?@delnan-谢谢你的链接,现在我正在浏览它,但我使用的基准是(很高兴修复那里的任何问题)。我创建了一个新方法,它只需调用带有四个重载的IndexOf
,并在其他测试中抛出它。)@keyboard还有一件事,第二篇文章似乎没有链接到。您似乎已经涵盖了第1部分和第2部分,代码方面(我不知道您是如何测量的,即外部VS,在发布模式下编译等)。哇,感谢您的努力!性能差异似乎可以忽略不计(对于不区分大小写的搜索),因此归结为设计决策。我想这真的可能只是一种情况,如果4重载签名发生变化,就必须避免两次代码更改(无论多么不可能)
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"));
...
}