我正在尝试优化一种方法。
原始方法如下:
private const string Separator = "::";
private char[] SeparatorChars = Separator.ToCharArray();
public string GetSubName(string name)
{
if (name.Contains(Separator))
{
return name.Split(SeparatorChars)[0];
}
return "INVALID";
}
如果
name
不包含::
,则返回INVALID
,否则返回Split
与::
生成的数组中的第一个元素。
我写了以下优化方法:
public string GetSubNameOpt(string name)
{
var index = name.IndexOf(Separator, StringComparison.Ordinal);
if (index >= 0)
{
return name.AsSpan().Slice(0, index).ToString();
}
return "INVALID";
}
目标是省略字符串上的第二次 O(N) 迭代,以便在字符串包含
::
子字符串时将其拆分。
我使用 BenchmarkDotNet 对这两种方法进行了基准测试,结果如下
现在..正如预期的那样,由于使用了 AsSpan 并删除了字符串的 1 O(N) 迭代,优化方法在“名称包含 :: case”中的时间和内存方面更好,但令我惊讶的是非优化方法对于无效情况更好。
编辑 使用非空、不包含
::
的情况运行。
同样,优化方法速度较慢...
您能解释一下是什么原因导致了这种行为吗?
string.Contains(string)
(即没有明确指定 StringComparison
)在 .NET 的最新实现中是一个非常特殊的野兽,看起来像下面这样:
public bool Contains(string value)
{
if (value == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
return SpanHelpers.IndexOf(
ref _firstChar,
Length,
ref value._firstChar,
value.Length) >= 0;
}
当 string.Contains(string, StringComparison)
重载时,只需调用
IndexOf
:
public bool Contains(string value, StringComparison comparisonType)
{
return IndexOf(value, comparisonType) >= 0;
}
即name.Contains(Separator, StringComparison.Ordinal)
将产生“预期”的性能。和
IndexOf
用于序数比较,当前使用内部
Ordinal.IndexOf
,其中包含针对更一般用例的大量检查和不同优化。
您可以尝试的一件事是使用 AsSpan().IndexOf
,它在我的机器上可以为优化版本带来更好的性能:
public string name { get; set; } = "qwerty";
private const string Separator = "::";
private char[] SeparatorChars = Separator.ToCharArray();
[Benchmark]
public string GetSubName()
{
if (name.Contains(Separator))
{
return name.Split(SeparatorChars)[0];
}
return "INVALID";
}
[Benchmark]
public string GetSubNameOpt()
{
var index = name.AsSpan().IndexOf(SeparatorChars);
if (index >= 0)
{
return name.AsSpan().Slice(0, index).ToString();
}
return "INVALID";
}