Freigeben über


StringBuilder.ToString() vs Reflection

I had an intriguing conversation with a friend on Friday about the performance of using the StringBuilder.ToString() method to return the value of the String contained in the StringBuilder object.  Following this conversation, I decided to take a deeper look at how StringBuilder works.  Here is the StringBuilder.ToString() method code from Reflector:

 public override string ToString()
{
  string stringValue = this.m_StringValue;
  if (this.m_currentThread != Thread.InternalGetCurrentThread())
  {
    return string.InternalCopy(stringValue);
  }
  if ((2 * stringValue.Length) < stringValue.ArrayLength)
  {
    return string.InternalCopy(stringValue);
  }
  stringValue.ClearPostNullChar();
  this.m_currentThread = IntPtr.Zero;
  return stringValue;
}

As you can see, there is a bit of overhead to this method.  It performs some thread-safe checks and some bounding checks.  This overhead is typically minimal, but it can be quite expensive as the size of the String grows.  I decided to use a code timer (taken from Vance Morrison) to test how much the overhead grows as the String size is incremented.  As expected, the time taken to execute the method grows at about the same rate as the size of the String.  If the size of the String is doubled, then the time taken to execute the method is roughly doubled.  (BTW, I am performing this test on a Pentium 4 HT 3.60GHz, 4GB RAM, and 64-bit OS).

This does not seem to be a problem for typical small strings (<1K).   However, if you get over 1-2K, the time taken to execute the method becomes very costly.  So what if we could get the String value in the StringBuilder another way without all of the ToString() method overhead.  Well, we can do that using reflection:

 public static string GetStringValue(StringBuilder sb)
{
  lock (sb)
  {
    return (string)sb.GetType().GetField("m_StringValue", 
      BindingFlags.Instance | BindingFlags.NonPublic).GetValue(sb);
  }
}

"m_StringValue" is the internal field in which StringBuilder stores the value of the string.  As we all know, reflection also comes with some costly overhead.  After doing a comparison, if the String in the StringBuilder is 20 characters in length, ToString() is roughly 175 times faster than using reflection.  Below are some other comparisons that I did:

Length Results (approximates)
20 ToString() 175 times faster than Reflection
100 ToString() 100 times faster than Reflection
1000 ToString() 10 times faster than Reflection
10000 ToString() the same as Reflection
20000 ToString() 1.6 times slower than Reflection
50000 ToString() 8.5 times slower than Reflection

 As you can see, as the String gets larger the time taken to execute also increases.  However, the time taken to get the value using reflection stays constant regardless of the length of the String. 

Now, when are you ever going to use StringBuilder to manipulate a 50K string?  Not a very common task.   But if you ever do need to do that, you might be able to save a little execution time by utilizing reflection to get the value.

Comments

  • Anonymous
    October 20, 2007
    PingBack from http://msdnrss.thecoderblogs.com/2007/10/20/stringbuildertostring-vs-reflection/

  • Anonymous
    October 24, 2007
    Not a whole lot of links this week. I have been pretty pushed getting some stuff ready for production.

  • Anonymous
    November 04, 2007
    Isnt the overhead you refer to of calling ToString on StringBuilder only occuring when the size is less then 1/2 of the capacity? i.e. in that case it is forcing a copy for efficiency. btw you could also get the string like this: SerializationInfo info = new SerializationInfo(typeof(StringBuilder), new FormatterConverter()); ((ISerializable)builder).GetObjectData(info, new StreamingContext()); String s = info.GetString("m_StringValue");

  • Anonymous
    November 05, 2007
    GlenSummers,  Thanks for the code that gets the internal string using SerializationInfo!  This proves to take about 1/10th (on average) of the time that the method of reflection that I proposed.

  • Anonymous
    September 17, 2008
    The comment has been removed

  • Anonymous
    September 17, 2008
    The comment has been removed