How to cast a value of generic type T to double without boxing?

NOTE: There was a bug in my initial code for instance-based code generation. Please re-check the code below. The changed part is the order of loading values onto the stack (ie. the .Emit lines). Both the code in the answer and the repository has been fixed.

If you want to go the route of code generation, as you hint to in your question, here's sample code:

It executes ConsumeValue (which does nothing in my example) 10 million times, on an array of ints and an array of booleans, timing the execution (it runs all the code once, to remove JIT overhead from skewing the timing.)

The output:

F1 ints = 445ms         <-- uses Convert.ToDouble
F1 bools = 351ms
F2 ints = 159ms         <-- generates code on each call
F2 bools = 167ms
F3 ints = 158ms         <-- caches generated code between calls
F3 bools = 163ms

Roughly 65% less overhead with code generation.

The code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace ConsoleApplication15
{
    class Program
    {
        public static void F1<T>(IList<T> values) where T : struct
        {
            foreach (T value in values)
                ConsumeValue(Convert.ToDouble(value));
        }

        public static Action<T> GenerateAction<T>()
        {
            DynamicMethod method = new DynamicMethod(
                "action", MethodAttributes.Public | MethodAttributes.Static,
                CallingConventions.Standard,
                typeof(void), new Type[] { typeof(T) }, typeof(Program).Module,
                false);
            ILGenerator il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0); // get value passed to action
            il.Emit(OpCodes.Conv_R8);
            il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
            il.Emit(OpCodes.Ret);

            return (Action<T>)method.CreateDelegate(typeof(Action<T>));
        }

        public static void F2<T>(IList<T> values) where T : struct
        {
            Action<T> action = GenerateAction<T>();
            foreach (T value in values)
                action(value);
        }

        private static Dictionary<Type, object> _Actions =
            new Dictionary<Type, object>();
        public static void F3<T>(IList<T> values) where T : struct
        {
            Object actionObject;
            if (!_Actions.TryGetValue(typeof(T), out actionObject))
            {
                actionObject = GenerateAction<T>();
                _Actions[typeof (T)] = actionObject;
            }
            Action<T> action = (Action<T>)actionObject;
            foreach (T value in values)
                action(value);
        }

        public static void ConsumeValue(double value)
        {
        }

        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();

            int[] ints = Enumerable.Range(1, 10000000).ToArray();
            bool[] bools = ints.Select(i => i % 2 == 0).ToArray();

            for (int pass = 1; pass <= 2; pass++)
            {
                sw.Reset();
                sw.Start();
                F1(ints);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F1 ints = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                F1(bools);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F1 bools = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                F2(ints);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F2 ints = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                F2(bools);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F2 bools = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                F3(ints);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F3 ints = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                F3(bools);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F3 bools = "
                        + sw.ElapsedMilliseconds + "ms");
            }
        }
    }
}

Note that if you make GenerationAction, F2/3 and ConsumeValue non-static, you have to change the code slightly:

  1. All Action<T> declarations becomes Action<Program, T>

  2. Change the creation of the DynamicMethod to include the "this" parameter:

     DynamicMethod method = new DynamicMethod(
         "action", MethodAttributes.Public | MethodAttributes.Static,
         CallingConventions.Standard,
         typeof(void), new Type[] { typeof(Program), typeof(T) },
         typeof(Program).Module,
         false);
    
  3. Change the instructions to load the right values at the right times:

     il.Emit(OpCodes.Ldarg_0); // get "this"
     il.Emit(OpCodes.Ldarg_1); // get value passed to action
     il.Emit(OpCodes.Conv_R8);
     il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
     il.Emit(OpCodes.Ret);
    
  4. Pass "this" to the action whenever it is called:

     action(this, value);
    

Here's the complete changed program for non-static methods:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace ConsoleApplication15
{
    class Program
    {
        public void F1<T>(IList<T> values) where T : struct
        {
            foreach (T value in values)
                ConsumeValue(Convert.ToDouble(value));
        }

        public Action<Program, T> GenerateAction<T>()
        {
            DynamicMethod method = new DynamicMethod(
                "action", MethodAttributes.Public | MethodAttributes.Static,
                CallingConventions.Standard,
                typeof(void), new Type[] { typeof(Program), typeof(T) },
                typeof(Program).Module,
                false);
            ILGenerator il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0); // get "this"
            il.Emit(OpCodes.Ldarg_1); // get value passed to action
            il.Emit(OpCodes.Conv_R8);
            il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
            il.Emit(OpCodes.Ret);

            return (Action<Program, T>)method.CreateDelegate(
                typeof(Action<Program, T>));
        }

        public void F2<T>(IList<T> values) where T : struct
        {
            Action<Program, T> action = GenerateAction<T>();
            foreach (T value in values)
                action(this, value);
        }

        private static Dictionary<Type, object> _Actions =
            new Dictionary<Type, object>();
        public void F3<T>(IList<T> values) where T : struct
        {
            Object actionObject;
            if (!_Actions.TryGetValue(typeof(T), out actionObject))
            {
                actionObject = GenerateAction<T>();
                _Actions[typeof (T)] = actionObject;
            }
            Action<Program, T> action = (Action<Program, T>)actionObject;
            foreach (T value in values)
                action(this, value);
        }

        public void ConsumeValue(double value)
        {
        }

        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();

            Program p = new Program();
            int[] ints = Enumerable.Range(1, 10000000).ToArray();
            bool[] bools = ints.Select(i => i % 2 == 0).ToArray();

            for (int pass = 1; pass <= 2; pass++)
            {
                sw.Reset();
                sw.Start();
                p.F1(ints);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F1 ints = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                p.F1(bools);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F1 bools = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                p.F2(ints);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F2 ints = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                p.F2(bools);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F2 bools = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                p.F3(ints);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F3 ints = "
                        + sw.ElapsedMilliseconds + "ms");

                sw.Reset();
                sw.Start();
                p.F3(bools);
                sw.Stop();
                if (pass == 2)
                    Console.Out.WriteLine("F3 bools = "
                        + sw.ElapsedMilliseconds + "ms");
            }
        }
    }
}

It's a good question, I also had this task and I came up using compiled Linq Expressions to do arbitrary conversions of value types to and from generic type parameters avoiding boxing. The solution is very effective and fast. It stores one compiled lambda per value type in a singleton. Usage is clean and readable.

Here's a simple class that does the job very well:

public sealed class BoxingSafeConverter<TIn, TOut>         
{
    public static readonly BoxingSafeConverter<TIn, TOut> Instance = new BoxingSafeConverter<TIn, TOut>();
    private readonly Func<TIn, TOut> convert;        

    public Func<TIn, TOut> Convert
    {
        get { return convert; }
    }

    private BoxingSafeConverter()
    {
        if (typeof (TIn) != typeof (TOut))
        {
            throw new InvalidOperationException("Both generic type parameters must represent the same type.");
        }
        var paramExpr = Expression.Parameter(typeof (TIn));
        convert = 
            Expression.Lambda<Func<TIn, TOut>>(paramExpr, // this conversion is legal as typeof(TIn) = typeof(TOut)
                paramExpr)
                .Compile();
    }
}

Now imagine that you want to have some storage with objects and doubles and you don't want your doubles to be boxed. You could write such class with generic getters and setters in the following way:

public class MyClass
{
    readonly List<double> doubles = new List<double>(); // not boxed doubles
    readonly List<object> objects = new List<object>(); // all other objects

    public void BoxingSafeAdd<T>(T val)
    {
        if (typeof (T) == typeof (double))
        {
            // T to double conversion
            doubles.Add(BoxingSafeConverter<T, double>.Instance.Convert(val));
            return;
        }

        objects.Add(val);
    }

    public T BoxingSafeGet<T>(int index)
    {
        if (typeof (T) == typeof (double))
        {
            // double to T conversion
            return BoxingSafeConverter<double, T>.Instance.Convert(doubles[index]);
        }

        return (T) objects[index]; // boxing-unsage conversion
    }
}

Here are some simple performance and memory tests of MyClass which show that using unboxed values can save you a lot of memory, reduce GC pressure and performance overhead is very tiny: just around 5-10%.

1. With boxing:

        const int N = 1000000;
        MyClass myClass = new MyClass();

        double d = 0.0;
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++, d += 0.1)
        {
            myClass.BoxingSafeAdd((object)d);
        }
        Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds);

        Console.WriteLine("Memory: {0} MB.", (double)GC.GetTotalMemory(false) / 1024 / 1024);

Results:

Time: 130 ms
Memory: 19.7345771789551 MB

2. Without boxing

        const int N = 1000000;
        MyClass myClass = new MyClass();

        double d = 0.0;
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++, d += 0.1)
        {
            myClass.BoxingSafeAdd(d);
        }
        Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds);

        Console.WriteLine("Memory: {0} MB", (double)GC.GetTotalMemory(false) / 1024 / 1024);

Results:

Time: 144 ms
Memory: 12.4955024719238 MB

Tags:

.Net