Performance of IConvertible operations: which operation is fastest? [Kit George]

I saw this question recently:

"Double d = ((IConvertible) o).ToDouble( System.Threading.Thread.CurrentThread.CurrentCulture);

Is this the best way (speed, correctness) to convert a boxed instance of one primitive types below to a double?"

I thought I'd break this down a little, since we often see questions about 'what's the best way to convert a boxed type'. I'm going to assume we're talking about converting to a double, although the analysis is the same for all primitive types. Summary? Assuming you don't know the type of the object your attempting to convert, Double.ToDouble is your best option, although Convert.ToDouble is basically the same. IConvertible is NOT a good option.

The first thing to simply check off the list is 'do you know if you're object is a double'. Now I know, I know, if you knew that, then why is it in an object? I really just want to make sure we get obvious scenario A out of the way: if you know the boxed object is a double, then simply unbox. You'll see the numbers below bear out this is far and away the cheapest, and best way to convert a boxed object.

So now we've got the easiest option out of the way, the more interesting scenario really is: you know object o CAN be converted to a double, but you don't know exactly what type it is: what do you do now?

There's 4 options we have, 3 of them seemingly good choices, and one of them admittedly silly, but I have seen code that does it, so I'm including it just to ensure we can rule it out as a realistic option.

  1. Use IConvertible.ToDouble. The unfortunate thing here is that you have to pass this sucker an IFormatProvider, even if it's the default provider for the thread (as in the scenario above). Note the way we do this is the same as the question above.
  2. Use Double.ToDouble. The key question here if there's a difference in performance from the above is: why? Isn't this ust the concrete implementation of IConvertible on Double?
  3. Use Convert.ToDouble. This wouldn't seem to be any faster than the above, surely, but it is an option.
  4. Use Double.Parse. The silly option, because you ahve to turn your object into a string, before doing the operation

So here's the code I wrote to try this out. I call all of these methods once because this is a perf test, and we want to make sure that there's no issue with priming calls, etc.

using System;
using System.Diagnostics;

namespace ConvertTest
{
class Program
{
static Stopwatch sw = null;
static int iterations = 1000000;
static object o = 5d;
// static object o = 5; // this is formed as an int

    static void Main(string[] args)
{
Console.WriteLine("Performance of different conversions from an object to a double\r\n");
Console.WriteLine("{0,-23}{1,10}", "Call Name", "Cost");
Console.WriteLine("-----------------------------------");

 sw = new Stopwatch();

DoubleCast();
IConvertibeToDouble();
DoubleToDouble();
ConvertToDouble();
DoubleParse();
Console.WriteLine(DoubleCast());
Console.WriteLine(IConvertibeToDouble());
Console.WriteLine(DoubleToDouble());
Console.WriteLine(ConvertToDouble());
Console.WriteLine(DoubleParse());

 Console.WriteLine("-----------------------------------");
}

    static string DoubleCast() {
sw.Reset();
sw.Start();

 for (int i=0;i<iterations;i++) {
Double d = (Double)o;
if (d>5) {}
}
sw.Stop();
return String.Format("{0,-23}{1,10}", "double cast", sw.ElapsedTicks);
}

    static string IConvertibeToDouble() {
sw.Reset();
sw.Start();

 for (int i=0;i<iterations;i++) {
Double d = ((IConvertible)o).ToDouble( System.Threading.Thread.CurrentThread.CurrentCulture);
if (d>5) {}
}
sw.Stop();
return String.Format("{0,-23}{1,10}", "IConvertible.ToDouble", sw.ElapsedTicks);
}

    static string DoubleToDouble() {
sw.Reset();
sw.Start();

 for (int i=0;i<iterations;i++) {
Double d = Convert.ToDouble(o);
if (d>5) {}
}
sw.Stop();
return String.Format("{0,-23}{1,10}", "Double.ToDouble", sw.ElapsedTicks);
}

    static string ConvertToDouble() {
sw.Reset();
sw.Start();

 for (int i=0;i<iterations;i++) {
Double d = Convert.ToDouble(o);
if (d>5) {}
}
sw.Stop();
return String.Format("{0,-23}{1,10}", "Convert.ToDouble", sw.ElapsedTicks);
}

    static string DoubleParse() {
sw.Reset();
sw.Start();

 for (int i=0;i<iterations;i++) {
Double d = Double.Parse(o.ToString());
if (d>5) {}
}
sw.Stop();
return String.Format("{0,-23}{1,10}", "Double.Parse", sw.ElapsedTicks);
}
}
}

Now let's first look at the scenario where you know your type is going to be a double. In this scenario, pretty typical numbers I got were like this:

Call Name Cost
-----------------------------------
double cast 10769
IConvertible.ToDouble 120936
Double.ToDouble 59601
Convert.ToDouble 59683
Double.Parse 2627561
-----------------------------------

Casting is clearly the winner, around 6 times faster than the nearest rival. Note the terrible performance of Double.Parse for this scenario: to be expected. What's interesting is that the IConvertible implementation is twice as slow as Double.ToDouble (and on Convert.ToDouble which is in a deadheat).

Before making a judgement, let's have a look at numbers for converting an int to a double. Note you'll have to comment out the doublecast test, since it's clearly no longer allowed, and declare the instance of o as an int. Here's some typical numbers:

Call Name Cost
-----------------------------------
IConvertible.ToDouble 124897
Double.ToDouble 60663
Convert.ToDouble 61111
Double.Parse 1644313
-----------------------------------

Wow! Double.ToDouble is still the winner, again, with Convert being perfectly viable. Well, let's go and ildasm the assembly we built using the code above and see what's going on. The call to IConvertible.ToDouble results in the following:

castclass [mscorlib]System.IConvertible
call class [mscorlib]System.Threading.Thread [mscorlib]System.Threading.Thread::get_CurrentThread()
callvirt instance class [mscorlib]System.Globalization.CultureInfo [mscorlib]System.Threading.Thread::get_CurrentCulture()
callvirt instance float64 [mscorlib]System.IConvertible::ToDouble(class [mscorlib]System.IFormatProvider)

And in contrast, Double.ToDouble results in the following:

call float64 [mscorlib]System.Convert::ToDouble(object)

The key thing to see here is that the first set of code did NOT simply call through to the IConvertible imeplmentation of ToDouble on the Double class. Instead, it generated a whole lot of costly calls.

Convert Basically results in a single additional call since it just calls through to ToDouble on Double: this is why it's so close in performance.

So Double.ToDouble wins when you don't know what primitive you're converting. If you know it's a double, you get a single unbox call in the il, and of course, we've optimized the heck out of that baby. The other interesting thing above is that Parse got quite markedly faster: fundamentally because formatting an int is remarkably simpler than formatting a double, not becausre it parses that much better (although there's a bit of that going on too).