01 July 2017

StringBuilder over string concatenation

Recently I was going through a code in my personal project written long back. The legacy code was using string concatenation to build a text in a loop which ran 75 iterations on average. In each loop, there were 4 string concatenations. So totally it was doing 300 string concatenations on one run. Long back I learned its better to use StringBuilder when 5 or move string concatenation is involved. So I wanted to see how StringBuilder helps with performance over string concatenation.

Code Performance is not just about execution time, it is just one metric among many important ones!

I did code execution time comparison using Stopwatch timer to build a same text using string concatenation and also by using StringBuilder class and I found NO time difference between the them at times. Even when there is an execution time difference it was negligible. Milliseconds difference over many iterations was not exciting!"

Then I realized "the time it took to complete a task, is just one metric to measure the performance of the code among many important other metrics, hence we also need to consider other metrics around .NET Memory Management i.e, Number of Generation 0 Collection, etc. So started to track the .NET CLR Memory Performance counters.

String concatenation and measuring .NET CLR Memory Performance counters

Let's consider below C# code sample. The code is building a text using string concatenation approach.

static void Main(string[] args)
      {
        for (int i = 0; i < 10000; i++)
        {
        string text = null;
        for (int j = 0; j < 600; j++)
        {
          text += string.Format("Name{0}{1}", Guid.NewGuid().ToString(), i.ToString());
        }
        Console.WriteLine(i.ToString());
        }
        Console.ReadLine();
     }

When above code completes all looping iterations and waiting for you to key in ENTER to exit, have a look the .NET memory counters for the underlying windows process. For this blog post, I used Process Explorer tool as it serves the purpose by providing All counters I am interested and also it is easy to use plus free.






















StringBuilder to build text and measuring .NET CLR Memory Performance counters

Let's consider the below C# code to build the text using StringBuilder.

static void Main(string[] args)
      {
        for (int i = 0; i < 10000; i++)
        {
         StringBuilder text = new StringBuilder();
        for (int j = 0; j < 600; j++)
        {
         text.AppendFormat("Name{0}{1}", Guid.NewGuid().ToString(), i.ToString());
        }
        Console.WriteLine(i.ToString());
        }
        Console.ReadLine();
     }

When the above code completes all looping iterations and waiting for you to key in ENTER key to exit, have a look at the .NET memory counters for the underlying windows process. For this blog post, I used Process Explorer tool as it serves the purpose by providing All counters I am interested, also it is easy to use and free!






















Test conclusion

If you look the at .NET CLR Memory Management metrics for the two cases, we can see there is a huge difference in 'Gen 0 Collection', 'Promoted memory from Gen 0' and '% time in GC' measurements.

The code which is using string concatenation to combine strings is resulting in 75809 Gen 0 Garbage collection compared to the code which is using StringBuilder just resulting in 730 Gen 0 Garbage collection. So there is roughly 99% gain of garbage collection performance by employing StringBuilder over String concatenation.

Test results indicate, the code which is using string concatenation is witnessing more memory allocation and Garbage collection compared to the code which is using StringBuilder to do the same unit of work.

With this post I would like to drive a point. Whenever you sit for design discussion, coding and code review, just don't consider the speed of execution as the single metric to lean towards an approach. Also consider other metrics like garbage collection, CPU utilization etc to arrive at a solution.

Important notes

  • The tests were ran on INTEL i7 dual core processor, 8 GB RAM and WINDOWS 10 Operating system machine which is light on resource consumption. Not many programs running.
  • Depending on the load, the performance counters value differs. So for any performance tests, you will always need to consider the NFRs to be supported. Consider benchmarking the performance considering the Non Functional Requirements(NFRs).

1 comment: