title: C# ReadOnlySpan<char> vs Substring for string dissection
body: <p>I have a fairly simple string extension method that gets called very frequently in a system I have that is doing a lot of string manipulations. I read this post (<a href="https://stackoverflow.com/questions/51673659/string-substring-seems-to-bottleneck-this-code/51681829">String.Substring() seems to bottleneck this code</a>) and thought I would try the same method to see if I could find some performance by changing how I'm reading the string. My results are not quite what I was expecting (I was expecting ReadOnlySpan to provide a significant perf boost), and I'm wondering why that is. In my production code on a real run I found a very slight loss of performance.</p>
  
  <p>I generated a file with ~1.15 million rows of strings with the character I care about, called the method on each, and dumped the results to console. </p>
  
  <p>My results (runtime in milliseconds) are:</p>
  
  <pre><code>ReadOnlySpan.IndexOf Framework 4.7.1: 68538
  ReadOnlySpan.IndexOf Core 2.1: 64486
  
  ReadOnlySpan.SequenceEqual Framework 4.7.1: 63650
  ReadOnlySpan.SequenceEqual Core 2.1: 65071
  
  substring Framework 4.7.1: 63508
  substring Core 2.1: 64125
  </code></pre>
  
  <p><br>The code (all identical from Full Framework to Core 2.1):</p>
  
  <p>The calling code:</p>
  
  <pre><code>static void Main(string[] args)
  {
      Stopwatch sw = new Stopwatch();
      sw.Start();
  
      var f = File.ReadAllLines("periods.CSV");
  
      foreach (string s in f)
      { Console.WriteLine(s.CountOccurrences(".")); }
  
      sw.Stop();
      Console.WriteLine("Done in " + sw.ElapsedMilliseconds + " ms");
      Console.ReadKey();
  }
  </code></pre>
  
  <p><br>The original substring form of my method:</p>
  
  <pre><code>public static int CountOccurrencesSub(this string val, string searchFor)
  {
      if (string.IsNullOrEmpty(val) || string.IsNullOrEmpty(searchFor))
      { return 0; }
  
      int count = 0;
  
      for (int x = 0; x &lt;= val.Length - searchFor.Length; x++)
      {
          if (val.Substring(x, searchFor.Length) == searchFor)
          { count++; }
      }
  
      return count;
  }
  </code></pre>
  
  <p><br>The ReadOnlySpan version (which I've tested with both IndexOf and SequenceEqual for equality checks):</p>
  
  <pre><code>public static int CountOccurrences(this string val, string searchFor)
  {
      if (string.IsNullOrEmpty(val) || string.IsNullOrEmpty(searchFor))
      { return 0; }
  
      int count = 0;
  
      ReadOnlySpan&lt;char&gt; vSpan = val.AsSpan();
      ReadOnlySpan&lt;char&gt; searchSpan = searchFor.AsSpan();
  
      for (int x = 0; x &lt;= vSpan.Length - searchSpan.Length; x++)
      {
          if (vSpan.Slice(x, searchSpan.Length).SequenceEqual(searchSpan))
          { count++; }
      }
  
      return count;
  }
  </code></pre>
  
  <p><br>
  Does the equality comparison do an allocation in the methods I'm calling, and therefor no boost? Is this just not a good application for ReadOnlySpan? Am I just plain old missing something?</p>