Partilhar via


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).

Comments

  • Anonymous
    February 11, 2005
    I'm obviously missing something here.

    First of all...Double.ToDouble()? Is this new in Whidbey beta 2? I'm certainly not finding it in beta 1. Because in my copy, Convert.ToDouble(object) is just as bad as calling through IConvertible (in fact, that's what it appears to do). If this is beta 2 (or even if it's Whidbey in general, which it obviously is due to StopWatch), it would be nice for you to start out by mentioning that. People not playing around with the beta right now may be even more confused than I.

    Second of all...Your example for Double.ToDouble is calling Convert.ToDouble() and not Double.ToDouble().

    But aside from that, if what you say is true, them I'm glad to see some performance improvements on converting a boxed object. This will automatically help improve all ADO.NET code, since Data Readers methods all seem to always call Convert.ToXXX(). I assume it will also help VB non-Option-Strict code as well (although I'm not to familiar with how it is implemented under the hood).

  • Anonymous
    February 13, 2005
    As Matt said, your Double.ToDouble() example is actually invoking Convert.ToDouble(). Hopefully you just posted the wrong code?

    As for Double.ToDouble(), it is explicitly implemented so you'd have to invoke it like this:

    Double d = ((IConvertible) o).ToDouble();

    Kent

  • Anonymous
    February 14, 2005
    The comment has been removed

  • Anonymous
    February 15, 2005
    Just FYI, I run the same tests against Compact Framework 1.0 and with VB.NET

    Like here casting is the clear winner. DirectCast that is; CType or CDbl are equally slower than Convert.ToDouble which itself trails IConvertible.ToDouble.
    Note that for the IConvertibleToDouble case I passed in System.Globalization.CultureInfo.CurrentCulture (as the Threading culture is unavailable on CF).

  • Anonymous
    February 20, 2005
    Whoa, what machine is that?
    I get (release build and not started from vs)

    double cast 4159976-5958480
    IConvertible.ToDouble 91323260
    Double.ToDouble 48208260
    Convert.ToDouble 46969660
    Double.Parse 2269345148

    With 3 Ghz P4+HT

    Quite a big fluctuation on that double cast here. I have the December ctp..

  • Anonymous
    January 21, 2009
    PingBack from http://www.keyongtech.com/463230-converting-between-object-and-classes

  • Anonymous
    June 19, 2009
    PingBack from http://edebtsettlementprogram.info/story.php?id=22760