Tuesday, October 04, 2005 3:24 PM bart

Performance basics - use StringBuilder

Almost got gray hair when reading some code yesterday. The piece of C# code I was reading, was doing string concatenation to build a pretty large XML fragment (where the size was proportional to the number of rows in a database table, so not known at compile-time). If you don't know the number of concatenations at compile time, please use the System.Text.StringBuilder class. Strings in .NET are immutable, so any change you make to a string will lead to the birth of a new string in memory, leading to additional pressure on the garbage collector as well.

See it yourself with the next demo:

#define TEST1
#define TEST2
#define TEST3

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;

class Demo
{
 [DllImport("kernel32.dll")]
 internal static extern int QueryPerformanceCounter(out Int64 lpPerformanceCount);

 [DllImport("kernel32.dll")]
 internal static extern int QueryPerformanceFrequency(out Int64 lpPerformanceCount);

 public static void Main(string[] args)
 {
  if (args.Length != 1)
   goto __error;

  try
  {
   MAX = int.Parse(args[0]);

   if (MAX <= 0)
    goto __error;
  }
  catch
  {
   goto __error;
  }

#if TEST1
  Decimal t1 = Test(new BuildString(DemoWithStringBuilder));
  Console.WriteLine(t1);
#endif

#if TEST2
  Decimal t2 = Test(new BuildString(DemoWithStringBuilderAndFormat));
  Console.WriteLine(t2);
#endif

#if TEST3
  Decimal t3 = Test(new BuildString(DemoWithConcatenation));
  Console.WriteLine(t3);
#endif

  return;

 __error:
  Console.WriteLine("Usage: {0}.exe ", Assembly.GetExecutingAssembly().GetName().Name);
 }

 static decimal GetSecondsElapsed(long start, long stop)
 {
  long queryFrequency;
  QueryPerformanceFrequency(out queryFrequency);
  decimal result = Convert.ToDecimal(stop - start) / Convert.ToDecimal(queryFrequency);
  return Math.Round(result, 6);
 }

 private delegate string BuildString();

 private static int MAX = 1000000;

 private static string DemoWithConcatenation()
 {
  string s = "";
  for (int i = 0; i < MAX; i++)
   s = s + i + "\n";

  return s;
 }

 private static string DemoWithStringBuilder()
 {
  StringBuilder sb = new StringBuilder();
  for (int i = 0; i < MAX; i++)
   sb.Append(i + "\n");

  return sb.ToString();
 }

 private static string DemoWithStringBuilderAndFormat()
 {
  StringBuilder sb = new StringBuilder();
  for (int i = 0; i < MAX; i++)
   sb.AppendFormat("{0}\n", i);

  return sb.ToString();
 }

 private static decimal Test(BuildString build)
 {
  long start, stop;
  QueryPerformanceCounter(out start);

  build();

  QueryPerformanceCounter(out stop);
  return GetSecondsElapsed(start, stop);
 }
}

Start, Run, notepad, OK. Copy/paste the code above and run csc. Now execute the following:

>main 10000
0.007609
0.009746
0.768374

Wow, a factor 100 difference :o. If you have some free time left, try with a parameter of 100000 as well and get something to drink in the meantime. I hope you see the difference. Also check out the GC behavior, as follows:

  1. In Administrative tools, go to the Performance MMC.
  2. Remove all counters that are currently in the list.
  3. Add the # Gen 0 Collections, # Gen 1 Collections and # Gen 2 Collections counters from .NET CLR Memory performance object to the view.
  4. First modify the code to disable tests 2 and 3:

    #define TEST1
    //#define TEST2
    //#define TEST3
  5. Compile and run.
  6. Go to the Performance MMC and take a look at the number of collections.
  7. Now modify the code to enable only test 3:

    //#define TEST1
    //#define TEST2
    #define TEST3
  8. Compile and run again.
  9. Go to the Performance MMC and taak a look at the number of collections. Compare this number to the number you saw earlier.

The difference over here for generation 0 is 2 (with StringBuilder) to 936 (with concatenation). Please keep your (perf-savvy) code reviewers from getting gray hair and use the StringBuilder class :-).

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Comments

# Talking about System.Security.SecureString

Friday, March 31, 2006 2:14 PM by B# .NET Blog

Introduction
Within a couple of weeks, our series of MSDN Security Evenings will go on air at various...