【发布时间】:2013-07-09 00:39:49
【问题描述】:
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 方法不直接调用最终重载?
唯一的好处是,如果最终重载的签名发生变化,只需要进行一次更改(中间方法的更改),还是设计不止于此?
从 cmets 编辑(有关速度差异说明,请参阅更新 2)
为了澄清我得到的性能差异,以防我在某处犯了错误:
我跑了this benchmark(循环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方法实际上变得最慢。 IndexOf 和 Contains 在性能方面几乎不相上下。很明显,ToUpper 调用歪曲了为什么Contains 和IndexOf 之间存在如此差异的结果。
不确定为什么QuickContains 扩展方法变得最慢。 (可能与Contains 具有[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] 属性有关?)。
关于为什么不直接调用 4 重载方法的问题仍然存在,但该决定似乎并未影响性能(正如 Adrian 和 delnan 在 cmets 中指出的那样)。
【问题讨论】:
-
编译器可能会内联方法调用,因此在代码运行时,任何出现的
String.Contains(string value)很可能已经被重写为更复杂的版本(留下唯一的原因是您提到的优势,没有劣势)。然而,这显然只是猜测。 -
这是个好主意。我刚刚尝试通过创建一个直接调用最终重载的自定义
Contains方法对其进行基准测试,它最终比现有的Contains方法快50%,因此性能受到明显影响(尽管绝对值很小; 200ms 超过 1m 循环)。 -
@keyboardP 我觉得这有点难以置信;即使是最简单的 inlining heuristic 和最简单的 inliner 也应该使这两个变体无法区分。你确定你没有提交benchmarking mistakes?
-
@delnan - 感谢您的链接,现在通过它,但我使用的基准是this one(很高兴解决那里的任何问题)。我创建了一个新方法,它简单地调用具有四个重载的
IndexOf,并将其与其他测试一起抛出)。 -
@keyboardP 还有一件事,第二篇文章似乎没有链接到third。您似乎已经涵盖了第一部分和第二部分,代码方面(我无法告诉您 如何 测量它,即在 VS 之外,在发布模式下编译等)。
标签: c# string oop language-design contains