I am trying to optimize a method.
Here is the original method:
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";
}
if name doesn't contain ::, return INVALID else return the first element in the array by generated by Split with ::.
I have wrote the following optimized method:
public string GetSubNameOpt(string name)
{
var index = name.IndexOf(Separator, StringComparison.Ordinal);
if (index >= 0)
{
return name.AsSpan().Slice(0, index).ToString();
}
return "INVALID";
}
The goal was to omit the second O(N) iteration over the string in order to split it in case it contains the :: substring.
I benchmarked both method using BenchmarkDotNet and here are the results

Now.. while, as expected, the Optimized method is better in time and memory in the "name contains :: case" due usage of AsSpan and removing of 1 O(N) iteration of the string, it was surprise to me that the Non Optimized method was better for the INVALID case.
EDIT
Running with a non empty, not containing :: case.

Again, the Optimized method is slower...
Can you explain what is causing this behavior?
Because
string.Contains(string)(i.e. without explicitly specifiedStringComparison) is a very special beast in recent implementations of .NET and looks something like the following:While the
string.Contains(string, StringComparison)overload just callIndexOf:i.e.
name.Contains(Separator, StringComparison.Ordinal)will result in "expected" performance.And
IndexOffor ordinal comparison currently uses internalOrdinal.IndexOfwhich contains quite a lot of checks and different optimizations for more general use-case.One thing you can try is using
AsSpan().IndexOfwhich on my machine results in better performance for the optimized version: