Performance of Int32.Parse() vs Convert.ToInt()

Recently, Vishal pointed out the difference between these these two methods. This blog post attempts to answer Mahesh Chand's question about the performance difference. (Read: I'm such a geek that I just had to find out.)

The methodology is pretty simple. I use DateTime.Now to get the time at the beginning and end of of a run. The results are then averaged over several runs.

The crucial issue is where that timing code is placed. In this case, it's relatively straight-forward. I want to wrap the calls to the conversion methods. However, there is another other issue to deal with, before we can get meaningful numbers. Namely, one conversion from a string to an int is a trivial operation. So trivial in-fact, that you can't even time it using this approach.

Therefore, my test application stuffs an array full of strings. These strings are created using an instance of the Random object, which is used to generate pseudo-random numbers. I could have used one number in a loop and avoided the whole array. However, I wanted to make it as close to "real" as possible. That is, I wanted the inputs to potentially be any valid Int32. Plus, it's just plain dull to fill an array with the same number.

You may be getting the feeling that there are trade-offs to performance testing. It's similar to the concept in Quantum Physics that the very act of measuring may influence the results.

In this case, the measurement is relatively simple. My only concern is the possibility of a small gap of time between the measurements (start/end time) and the actions being timed.

The trade-off comes when you decide what code you want call between those time measurements. We can't avoid using a loop, as one call to either conversion method returns too quickly to be measured. I could have avoided using an array, by passing the return value from Random.Next().ToString() directly to the conversion methods. However, this involves two method calls. Array access should be faster then those two calls, so I decided to prepare an array and pass that to the test method.

---

The application accepts two parameters. The first is the number of runs over which it'll average. The second is the size of the array of strings, i.e. the number strings we'll be converting in each run. The parameters are positional, the first one being the number of runs, the second being the number of strings to convert. Note: It doesn't test for negative numbers, so don't pass any to the application.

A basic explanation of the code:

1) Grab the parameters passed to the application, used them if they are valid numbers, otherwise, I use default values.

2) Stuff an array with strings created from pseudo-random numbers.

3) Create an array to hold the TimeSpans that will be returned from each run.

4) Output header text to the console. (Displays number of runs, number of strings being used and the name of the first method being tested.)

5) Loop through the runs for the one of the conversion method, collecting each result in the the TimeSpan[].

6) Average the TimeSpans returned, and out put the result.

*) Repeat steps 4-6, except the "header" only includes the name of the other conversion function being tested. (And, of course, call the other conversion method at step 5.)

TimeSpan[] is reused, since both methods will be go through the same number of runs. Once we've outputted the data from the first run, we don't need those results.

-----

Some of my sample output:


Runs: 400
No. of Strings: 100

Testing Int32.Parse()
Average run time: 00:00:00.0000390

Testing Convert.ToInt()
Average run time: 00:00:00.0000390


Runs: 400
No. of Strings: 100000

Testing Int32.Parse()
Average run time: 00:00:00.0344531

Testing Convert.ToInt()
Average run time: 00:00:00.0345312


Runs: 400
No. of Strings: 500000

Testing Int32.Parse()
Average run time: 00:00:00.1724609

Testing Convert.ToInt()
Average run time: 00:00:00.1727343


Runs: 100
No. of Strings: 100000

Testing Int32.Parse()
Average run time: 00:00:00.0343750

Testing Convert.ToInt()
Average run time: 00:00:00.0343750


Runs: 100
No. of Strings: 500000

Testing Int32.Parse()
Average run time: 00:00:00.1723437

Testing Convert.ToInt()
Average run time: 00:00:00.1726562


Runs: 100
No. of Strings: 1000000

Testing Int32.Parse()
Average run time: 00:00:00.3442187

Testing Convert.ToInt()
Average run time: 00:00:00.3454687

And now, for the code. Note, the commenting is beyond sparse and there are few blank lines, since I wanted to save space on this page. Normally, I'm as loquacious in my commenting as I am in my posting. ;)

Please, feel free to let me know if I made any mistakes.

using System;
namespace Testing
{
    class IntConvertTest
    {
        [STAThread]
        static void Main(string[] args)
        {
            IntConvertTest ict = new IntConvertTest();
            int runs, numberOfStrings;

            // parse parameters, provide defaults if parsing fails
            try
            {
                runs = Int32.Parse(args[0]);
            }
            catch
            {
                runs = 100;
            }
            try
            {
                numberOfStrings = Int32.Parse(args[1]);
            }
            catch
            {
                numberOfStrings = 1000000;
            }

            ict.StartTests(runs,numberOfStrings);
        }
        private void StartTests(int runs, int numberOfStrings)
        {
            string[] strings = new string[numberOfStrings];
            Random rng = new Random();
            TimeSpan[] timeSpans = new TimeSpan[runs];
            // fill array with strings which represent our numbers
            for (int i = 0; i < strings.Length; i++)
            {
                strings[i] = rng.Next().ToString();
            }

            Console.WriteLine("Test parameters:");
            Console.Write("Runs: " + runs.ToString() );
            Console.WriteLine("No. of Strings: " + numberOfStrings.ToString() );
            Console.WriteLine("Testing Int32.Parse()");

            for (int i = 0; i < runs; i++)
            {    
                timeSpans[i] = RunTest(strings,"Int32.Parse()");
            }

            Console.WriteLine("Average run time: {0}\n",AverageRunTimes(timeSpans));
            Console.WriteLine("Testing Convert.ToInt()");

            for (int i = 0; i < runs; i++)
            {
                timeSpans[i] = RunTest(strings,"Convert.ToInt32()");
            }

            Console.WriteLine("Average run time: {0}\n",AverageRunTimes(timeSpans) );
        }
        private TimeSpan RunTest(string[] strings, string methodToTest)
        {
            DateTime startTime = new DateTime(0L);
            DateTime endTime = new DateTime(0L);

            if (methodToTest == "Int32.Parse()")
            {
                startTime = DateTime.Now;

                for (int i = 0;i < strings.Length; i++)
                {
                    Int32.Parse(strings[i]);
                }

                endTime = DateTime.Now;
            }  
            else if (methodToTest == "Convert.ToInt32()")
            {
                startTime = DateTime.Now;

                for (int i = 0;i < strings.Length; i++)
                {
                    Convert.ToInt32(strings[i]);
                }

                endTime = DateTime.Now;
            }    
            return endTime - startTime;
        }
        private TimeSpan AverageRunTimes(TimeSpan[] runTimes)
        {
            TimeSpan total = new TimeSpan(0L);

            foreach (TimeSpan ts in runTimes)
            {
                total += ts;
            }
            return TimeSpan.FromTicks(total.Ticks/runTimes.Length);
        }
    }
}