title: C# Unexpected performance results for different methods of parsing byte[] as hex string
body: <p>I have created 3 different methods for converting a <code>byte[]</code> into a hex string using the following format: <code>{ 0xx2, ... }</code> (<a href="https://dotnetfiddle.net/UOHHXo" rel="nofollow noreferrer">Working Demo</a>)</p>
  
  <pre><code>using System;
  using System.Diagnostics;
  using System.Text;
  
  public class Program
  {
      public delegate string ParseMethod(byte[] Msg);
      public class Parser 
      {
          public string Name { get; private set;}
          public ParseMethod Handler { get; private set; }
  
          public Parser(string name, ParseMethod method)
          {
              Name = name;
              Handler = method;
          }
      }
  
      public static void Main()
      {
          Parser HexA = new Parser("ToHexA", ToHexA);
          Parser HexB = new Parser("ToHexB", ToHexB);
          Parser HexC = new Parser("ToHexC", ToHexC);
  
          TestCorrectness(HexA);
          TestCorrectness(HexB);
          TestCorrectness(HexC);
  
          Console.WriteLine("Small Message Performance:");
          TestPerformance(HexA, MsgSize: 10, Iterations: 1000000);
          TestPerformance(HexB, MsgSize: 10, Iterations: 1000000);
          TestPerformance(HexC, MsgSize: 10, Iterations: 1000000);
          Console.WriteLine();
  
          Console.WriteLine("Large Message Performance:");
          TestPerformance(HexA, MsgSize: 500, Iterations: 1000000);
          TestPerformance(HexB, MsgSize: 500, Iterations: 1000000);
          TestPerformance(HexC, MsgSize: 500, Iterations: 1000000);
          Console.WriteLine();
      }
  
      private static void TestCorrectness(Parser parser)
      {
          Console.WriteLine("Testing Correctness of \"{0}(byte[] Msg)\"", parser.Name);
          Console.WriteLine("\"{0}\"", parser.Handler(new byte[]{}));
          Console.WriteLine("\"{0}\"", parser.Handler(new byte[]{ 97 }));
          Console.WriteLine("\"{0}\"", parser.Handler(new byte[]{ 97, 98, 99, 0, 100 }));
          Console.WriteLine();
      }
  
      private static void TestPerformance(Parser parser, int MsgSize, int Iterations)
      {
          Stopwatch sw = new Stopwatch();
          sw.Reset();
  
          byte[] Msg = new byte[MsgSize];
  
          sw.Start();
          for (uint i = 0; i &lt; Iterations; ++i)
          {
              parser.Handler(Msg);
          }
          sw.Stop();
  
          Console.WriteLine("Performance for \"{0}\", {1}", parser.Name, sw.Elapsed);
      }
  
      private static string ToHexA(byte[] buffer)
      {
          return
              (
                  "{ 0x" +
                      BitConverter.ToString(buffer).ToLower()
                          .Replace("-", ", 0x") +
                  " }"
              )
              .Replace(" 0x }", "}");
      }
  
      private static string ToHexB(byte[] buffer)
      {
          if (buffer.Length == 0) { return "{}"; }
  
          const string Preamble = "{ 0x";
          const string Delimiter = ", 0x";
          const string Epilogue = " }";
  
          string Msg = Preamble + buffer[0].ToString("x2");
          for (int i = 1; i &lt; buffer.Length; ++i)
          {
              Msg += Delimiter + buffer[i].ToString("x2");
          }
  
          return Msg += Epilogue;
      }
  
      private static string ToHexC(byte[] buffer)
      {
          if (buffer.Length == 0) { return "{}"; }
  
          const string Preamble = "{ 0x";
          const string Delimiter = ", 0x";
          const string Epilogue = " }";
  
          StringBuilder HexOut = new StringBuilder(
              Preamble.Length +
              (Delimiter.Length * (buffer.Length - 1)) +
              (2 * buffer.Length) +
              Epilogue.Length
          );
  
          HexOut.Append(Preamble);
          HexOut.Append(buffer[0].ToString("x2"));
          for (int i = 1; i &lt; buffer.Length; ++i)
          {
              HexOut.Append(Delimiter);
              HexOut.Append(buffer[i].ToString("x2"));
          }
          HexOut.Append(Epilogue);
  
          return HexOut.ToString();
      }
  }
  </code></pre>
  
  <p>Running this code I get the following performance statistics</p>
  
  <pre><code>Small Message Performance:
  Performance for "ToHexA", 00:00:01.3078387
  Performance for "ToHexB", 00:00:01.6939201
  Performance for "ToHexC", 00:00:01.2997903
  
  Large Message Performance:
  Performance for "ToHexA", 00:00:32.5230253
  Performance for "ToHexB", 00:04:23.4798762
  Performance for "ToHexC", 00:00:56.2404684
  </code></pre>
  
  <p>I find it surprising that <code>ToHexA</code> (relative to <code>ToHexC</code>) executes faster with a longer message but slower with a shorter message. As I understand it, <code>Replace()</code>, <code>+</code>, and <code>ToLower()</code> all have to perform a create/copy operation because strings are immutable.</p>
  
  <p>At the same time, I suspected the overhead for StringBuilder might make it less suitable for shorter messages yet it executes faster than <code>ToHexA</code> in this situation.</p>
  
  <p>The only thing I expected was the performance hit associated with <code>ToHexB</code> which is kind of an in between worst of both worlds scenario...</p>
  
  <p>So, what is going on with these two methods and their performance characteristics?</p>
  
  <p><strong><h1>Edit to make my intentions with this question more clear</h1></strong></p>
  
  <p>I am not particularly interested in exact performance characteristics of two methods that are closely performant. I am aware this will be different on different devices, architectures, background processes, etc etc. and better testing methods exist to control variables and what not</p>
  
  <p>My purpose in asking this question was to better understand why two noticeably dissimilar (in terms of performance) approaches stack up the way they do. The accepted answer explains the mechanics of <code>ToString</code> in a way that satisfies this question.</p>
  
  <p>One constraint of this question the exact formatting of the output was specific and (we can only muse why, I think it's pretty standard) for some reason not one of the standard output formats in C#/.NET; it was this unique format that led to the question: stacking multiple replace and concatenation operations against <code>StringBuilder</code></p>