Performance Profiling Parse vs. TryParse vs. ConvertTo

When programming in .Net you are presented with several different ways to extract a numerical value (I’m using an Int32 for my example) from a string. Recently I was looking at the differences between Parse, TryParse and ConvertTo. So I figured that I would use the new performance profiler included with Visual Studio Team System 2005 to figure out the performance differences between these three functions.

 

            The first of these functions, Parse, is one that should be familiar to any .Net developer. This function will take a string and attempt to extract an integer out of it and then return the integer. If it runs into something that it can’t parse then it throws a FormatException or if the number is too large an OverflowException. Also, it can throw an ArgumentException if you pass it a null value.

 

            TryParse is a new addition to the new .Net 2.0 framework that addresses some issues with the original Parse function. The main difference is that exception handling is very slow, so if TryParse is unable to parse the string it does not throw an exception like Parse does. Instead, it returns a Boolean indicating if it was able to successfully parse a number. So you have to pass into TryParse both the string to be parsed and an Int32 out parameter to fill in. We will use the profiler to examine the speed difference between TryParse and Parse in both cases where the string can be correctly parsed and in cases where the string cannot be correctly parsed.

 

            The Convert class contains a series of functions to convert one base class into another. I believe that Convert.ToInt32(string) just checks for a null string (if the string is null it returns zero unlike the Parse) then just calls Int32.Parse(string). I’ll use the profiler to confirm this and to see if using Convert as opposed to Parse has any real effect on performance.

 

            For a CPU bound application like this I’ll be using the sampling mode of the profiler. Sampling mode takes a snapshot of program state in some periodic cycle (I’ll be using clock cycles as my metric) and has a much lower overhead then instrumentation mode for a test like this. The three main test functions that I will be using are as follows:

 

private List<Int32> TestParse(String[] strings)

{

Int32 intValue;

List<Int32> intList = new List<int>();

for (int i = 0; i < 5000000; i++)

{

intList.Clear();

foreach (String str in strings)

{

try

{

intValue = Int32.Parse(str);

intList.Add(intValue);

}

catch (System.ArgumentException)

           { }

              catch (System.FormatException)

   { }

     catch (System.OverflowException)

             { }

}

}

return intList;

}

private List<Int32> TestTryParse(String[] strings)

{

Int32 intValue;

List<Int32> intList = new List<int>();

Boolean ret;

for (int i = 0; i < 5000000; i++)

{

intList.Clear();

foreach (String str in strings)

              {

              ret = Int32.TryParse(str, out intValue);

          if (ret)

                   {

                     intList.Add(intValue);

                   }

             }

   }

     return intList;

}

private List<Int32> TestConvert(String[] strings)

{

Int32 intValue;

List<Int32> intList = new List<int>();

for (int i = 0; i < 5000000; i++)

{

intList.Clear();

foreach (String str in strings)

{

try

{

intValue = Convert.ToInt32(str);

intList.Add(intValue);

}

catch (System.FormatException)

{ }

catch (System.OverflowException)

{ }

}

}

return intList;

}

            To test the performance when good strings are passed in I used the following set of strings: { "123", "4567", "7890", "1", "1231280", "10" }. Show below is calltree view with the Main program function set as the root and with just the nodes below it expanded. Looking at this view we can see that about equal time was spent in all three of the conversion functions.

 

 

            On the next screen, I’ve expanded some of the callstacks below the TestConvert, TestParse and TestTryParse functions. As you might have guessed, the callstacks down here are very similar, especially for TestConvert and TestParse. The only real difference between those two is that TestConvert is making a call toThread.get_CurrentCulture to check for a null string before it calls down to Number.ParseInt32. TestTryParse has the same basic structure as TestParse, except it calls Number.TryParseInt32 instead of Number.ParseInt32. But all three functions end up at the same System.Number.ParseNumber where they all spend about the same amount of time (Convert spends a little less but I do not think this is enough to be statistically significant).

 

 

            So if all of these functions take about the same time with good data, how about if we make them parse bad data? The second set of data that I will use will have some oddballs like invalid characters, nulls, overflows and empty strings along with a couple of valid strings to parse. Here is the string set: { "12345", null, "123", "1324dfs", "51235", String.Empty, "43", "4123412341234123412341234123412341234123" }. I did have to reduce the number of iterations in each loop down from 5000000 to 50000 (that’s two zeros removed if you don’t want to count) in order to get approximately the same number of samples. Shown below you can see the huge difference between TryParse, Parse and ConvertTo when you are using bad data.

 

 

            So while TryParse is only taking .5% of execution time Parse is taking 18% while Convert is taking 14%. The difference is, as we guessed, in the exception handling code. That is why Convert was faster then Parse as Convert handles the null string in the bad data set without throwing an exception. Shown below you can see where all the execution time is going to inside of the TestParse function. Most of the time is spent in the constructor for ArgumentNullException (in red) and in looking up the resource string for the exception (in blue).

            So hopefully this little walkthrough has convinced you that if you plan on parsing a large number of strings you should use the new TryParse function instead of the old Parse or ConvertTo functions. Exceptions are meant to be “exceptions to the rule” not something that you want to be happening often as it can kill your program’s performance. So if you expect a lot of strings that you cannot parse make sure to use TryParse.